com.kenai.redminenb.issue.RedmineIssue.java Source code

Java tutorial

Introduction

Here is the source code for com.kenai.redminenb.issue.RedmineIssue.java

Source

/*
* Copyright 2012 Mykolas and Anchialas.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kenai.redminenb.issue;

import com.kenai.redminenb.Redmine;
import com.kenai.redminenb.repository.RedmineRepository;
import com.kenai.redminenb.util.ExceptionHandler;
import com.kenai.redminenb.util.SafeAutoCloseable;
import com.taskadapter.redmineapi.Include;
import com.taskadapter.redmineapi.RedmineException;
import com.taskadapter.redmineapi.bean.Attachment;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.apache.commons.lang.StringUtils;
import org.netbeans.modules.bugtracking.api.Issue;
import org.netbeans.modules.bugtracking.spi.IssueController;
import org.netbeans.modules.bugtracking.spi.IssueScheduleInfo;
import org.netbeans.modules.bugtracking.spi.IssueStatusProvider;
import org.openide.util.Mutex;
import org.openide.util.NbBundle.Messages;

/**
 *
 * @author Mykolas
 * @author Anchialas <anchialas@gmail.com>
 */
@Messages({ "# {0} - Tracker Name", "# {1} - Issue ID", "# {2} - Issue subject", "CTL_Issue={0} #{1}: {2}",
        "CTL_NewIssue=New Issue",
        //
        "CTL_Issue_Id=ID", "CTL_Issue_Id_Desc=Issue ID", "CTL_Issue_Project=Project",
        "CTL_Issue_Project_Desc=Project", "CTL_Issue_Tracker=Tracker", "CTL_Issue_Tracker_Desc=Issue Type",
        "CTL_Issue_ParentId=Parent task", "CTL_Issue_ParentId_Desc=Parent task", "CTL_Issue_StatusName=Status",
        "CTL_Issue_StatusName_Desc=Issue Status", "CTL_Issue_Category=Category",
        "CTL_Issue_Category_Desc=Issue Category", "CTL_Issue_PriorityText=Priority",
        "CTL_Issue_PriorityText_Desc=Issue Priority", "CTL_Issue_Subject=Subject", // Summary in Redmine
        "CTL_Issue_Subject_Desc=Issue Summary", "CTL_Issue_Author=Author", // Reporter in Redmine
        "CTL_Issue_Author_Desc=Issue Author", // Reporter in Redmine
        "CTL_Issue_Assignee=Assigned To", "CTL_Issue_Assignee_Desc=User to whom the issue is assigned",
        "CTL_Issue_CreatedOn=Created", "CTL_Issue_CreatedOn_Desc=creation time of the issue",
        "CTL_Issue_UpdatedOn=Updated", // Modification in Redmine
        "CTL_Issue_UpdatedOn_Desc=Last time the issue was modified", "CTL_Issue_TargetVersion=Target Version",
        "CTL_Issue_TargetVersion_Desc=Issue Target Version" })
public final class RedmineIssue {
    private static final Logger LOG = Logger.getLogger(RedmineIssue.class.getName());

    static final String FIELD_ID = "id"; // NOI18N
    static final String FIELD_PROJECT = "project"; // NOI18N
    static final String FIELD_SUBJECT = "subject"; // NOI18N
    static final String FIELD_PARENT = "parentId"; // NOI18N
    static final String FIELD_ASSIGNEE = "assignee"; // NOI18N
    static final String FIELD_AUTHOR = "author"; // NOI18N
    static final String FIELD_PRIORITY_ID = "priorityId"; // NOI18N
    static final String FIELD_PRIORITY_TEXT = "priorityText"; // NOI18N
    static final String FIELD_DONERATIO = "doneRatio"; // NOI18N
    static final String FIELD_ESTIMATED_HOURS = "estimatedHours"; // NOI18N
    static final String FIELD_SPENT_HOURS = "spentHours"; // NOI18N
    static final String FIELD_START_DATE = "startDate"; // NOI18N
    static final String FIELD_DUE_DATE = "dueDate"; // NOI18N
    static final String FIELD_TRACKER = "tracker"; // NOI18N
    static final String FIELD_STATUS_ID = "statusId"; // NOI18N
    static final String FIELD_STATUS_NAME = "statusName"; // NOI18N
    static final String FIELD_DESCRIPTION = "description"; // NOI18N
    static final String FIELD_CREATED = "createdOn"; // NOI18N
    static final String FIELD_UPDATED = "updatedOn"; // NOI18N
    static final String FIELD_VERSION = "targetVersion"; // NOI18N
    static final String FIELD_CATEGORY = "category"; // NOI18N
    //
    static final DateFormat DATETIME_FORMAT = DateFormat.getDateTimeInstance();

    private com.taskadapter.redmineapi.bean.Issue issue = new com.taskadapter.redmineapi.bean.Issue();
    private RedmineRepository repository;
    private RedmineIssueController controller;

    private final PropertyChangeSupport support;

    private Object localSummary;
    private Object localDescription;

    public RedmineIssue(RedmineRepository repo) {
        repository = repo;
        support = new PropertyChangeSupport(this);
    }

    public RedmineIssue(RedmineRepository repo, String summary, String description) {
        repository = repo;
        support = new PropertyChangeSupport(this);
        issue.setSubject(summary);
        issue.setDescription(description);
    }

    public RedmineIssue(RedmineRepository repository, com.taskadapter.redmineapi.bean.Issue issue) {
        this(repository);
        setIssue(issue);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        support.removePropertyChangeListener(listener);
    }

    private Integer busy = 0;

    private final SafeAutoCloseable busyHelper = new SafeAutoCloseable() {
        @Override
        public void close() {
            setBusy(false);
        }
    };

    public SafeAutoCloseable busy() {
        setBusy(true);
        return busyHelper;
    }

    public synchronized boolean isBusy() {
        return busy != 0;
    }

    private synchronized void setBusy(boolean busyBool) {
        final boolean oldBusy = isBusy();
        if (busyBool) {
            busy++;
        } else {
            busy--;
        }
        if (busy < 0) {
            throw new IllegalStateException("Inbalanced busy/nonbusy");
        }
        Mutex.EVENT.writeAccess(new Runnable() {
            @Override
            public void run() {
                support.firePropertyChange("busy", oldBusy, busy != 0);
            }
        });
    }

    public String getDisplayName() {
        return getDisplayName(issue);
    }

    public static String getDisplayName(com.taskadapter.redmineapi.bean.Issue issue) {
        if (issue == null) {
            return Bundle.CTL_NewIssue();
        }
        return issue.getId() == null ? issue.getSubject()
                : Bundle.CTL_Issue(issue.getTracker().getName(), issue.getId(), issue.getSubject());
    }

    public String getTooltip() {
        return getDisplayName();
    }

    public String getID() {
        return isNew() ? null : String.valueOf(issue.getId());
    }

    public String getSummary() {
        return isNew() ? Bundle.CTL_NewIssue() : issue.getSubject();
    }

    public boolean isNew() {
        return issue == null || issue.getId() == null || issue.getId() == 0;
    }

    public boolean hasParent() {
        return issue != null && issue.getParentId() != null;
    }

    public boolean isFinished() {
        // TODO: improve this
        return "closed".equalsIgnoreCase(issue.getStatusName());
    }

    void opened() {
        if (Redmine.LOG.isLoggable(Level.FINE)) {
            Redmine.LOG.log(Level.FINE, "issue {0} open start", new Object[] { getID() });
        }
        String refresh = System.getProperty("org.netbeans.modules.bugzilla.noIssueRefresh"); // NOI18N
        if (refresh != null && refresh.equals("true")) { // NOI18N
            return;
        }
        if (SwingUtilities.isEventDispatchThread()) {
            new SwingWorker<Object, Object>() {
                @Override
                protected Object doInBackground() throws Exception {
                    refresh();
                    return null;
                };
            }.execute();
        } else {
            refresh();
        }
        repository.scheduleForRefresh(getID());
        if (Redmine.LOG.isLoggable(Level.FINE)) {
            Redmine.LOG.log(Level.FINE, "issue {0} open finish", new Object[] { getID() });
        }
    }

    void closed() {
        if (Redmine.LOG.isLoggable(Level.FINE)) {
            Redmine.LOG.log(Level.FINE, "issue {0} close start", new Object[] { getID() });
        }
        repository.stopRefreshing(getID());
        if (Redmine.LOG.isLoggable(Level.FINE)) {
            Redmine.LOG.log(Level.FINE, "issue {0} close finish", new Object[] { getID() });
        }
    }

    public synchronized boolean refresh() {
        assert !SwingUtilities.isEventDispatchThread() : "Accessing remote host. Do not call in awt"; // NOI18N

        try {
            if (issue != null && issue.getId() != null) {
                setIssue(getRepository().getIssueManager().getIssueById(issue.getId(), Include.journals,
                        Include.attachments, Include.watchers));
            }
            return true;
        } catch (RedmineException | RuntimeException ex) {
            ExceptionHandler.handleException(LOG, "Can't refresh Redmine issue", ex);
        }
        return false;
    }

    public void addComment(String comment, boolean resolve) {
        Integer oldStatusId = issue.getStatusId();

        try {
            issue.setNotes(comment);

            if (resolve) {
                // TODO This works for default Redmine Settings only. Add resolved status ID configuration to Redmine Option.
                issue.setStatusId(3);
            }

            getRepository().getIssueManager().update(issue);

            return;

        } catch (RedmineException | RuntimeException ex) {
            ExceptionHandler.handleException(LOG, "Can't add comment for a Redmine issue", ex);
        }

        issue.setStatusId(oldStatusId);
    }

    public void attachFile(File file, String description, String comment, boolean patch) {
        try {
            Attachment a = getRepository().getAttachmentManager().uploadAttachment("application/octed-stream",
                    file);
            a.setDescription(description);
            issue.addAttachment(a);
            if (!StringUtils.isBlank(comment)) {
                issue.setNotes(comment);
            }
            getRepository().getIssueManager().update(issue);
        } catch (RedmineException | IOException ex) {
            ExceptionHandler.handleException(LOG, "Can't attach file to a Redmine issue", ex);
        }
    }

    public IssueController getController() {
        if (controller == null) {
            controller = new RedmineIssueController(this);
        }
        return controller;
    }

    public com.taskadapter.redmineapi.bean.Issue getIssue() {
        return issue;
    }

    public void setIssue(com.taskadapter.redmineapi.bean.Issue issue) {
        this.issue = issue;
        support.firePropertyChange(Issue.EVENT_ISSUE_DATA_CHANGED, null, null);
    }

    public RedmineRepository getRepository() {
        return repository;
    }

    @Override
    public String toString() {
        return getTooltip();
    }

    public IssueStatusProvider.Status getStatus() {
        if (issue.getId() == null) {
            return IssueStatusProvider.Status.OUTGOING_NEW;
        }
        if (localDescription != null || localSummary != null) {
            return IssueStatusProvider.Status.OUTGOING_MODIFIED;
        }
        return IssueStatusProvider.Status.SEEN;
    }

    Date getDueDate() {
        if (issue != null) {
            return issue.getDueDate();
        } else {
            return null;
        }
    }

    IssueScheduleInfo getSchedule() {
        if (issue != null && issue.getStartDate() != null) {
            return new IssueScheduleInfo(issue.getStartDate());
        } else {
            return null;
        }
    }

    void setSchedule(IssueScheduleInfo scheduleInfo) {
        if (issue == null) {
            return; // Silently igonre setSchedule on not yet saved issues
        }
        if (scheduleInfo == null) {
            issue.setStartDate(null);
        } else {
            issue.setStartDate(scheduleInfo.getDate());
        }
        getRepository().getRequestProcessor().execute(issueUpdate);
    }

    private Runnable issueUpdate = new Runnable() {
        @Override
        public void run() {
            try {
                getRepository().getIssueManager().update(issue);
            } catch (RedmineException | RuntimeException ex) {
                ExceptionHandler.handleException(LOG, "Failed to update start date for issue", ex);
            }
        }
    };
}