pt.ist.maidSyncher.domain.activeCollab.ACTask.java Source code

Java tutorial

Introduction

Here is the source code for pt.ist.maidSyncher.domain.activeCollab.ACTask.java

Source

/*******************************************************************************
 * Copyright (c) 2013 Instituto Superior Tcnico - Joo Antunes
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     Luis Silva - ACGHSync
 *     Joo Antunes - initial API and implementation
 ******************************************************************************/
package pt.ist.maidSyncher.domain.activeCollab;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.ObjectUtils;
import org.eclipse.egit.github.core.Issue;
import org.eclipse.egit.github.core.Label;
import org.eclipse.egit.github.core.Milestone;
import org.eclipse.egit.github.core.service.IssueService;
import org.eclipse.egit.github.core.service.LabelService;
import org.eclipse.egit.github.core.service.MilestoneService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pt.ist.maidSyncher.api.activeCollab.ACProject;
import pt.ist.maidSyncher.domain.MaidRoot;
import pt.ist.maidSyncher.domain.SynchableObject;
import pt.ist.maidSyncher.domain.activeCollab.exceptions.TaskNotVisibleException;
import pt.ist.maidSyncher.domain.dsi.DSIIssue;
import pt.ist.maidSyncher.domain.dsi.DSIMilestone;
import pt.ist.maidSyncher.domain.dsi.DSIObject;
import pt.ist.maidSyncher.domain.dsi.DSIProject;
import pt.ist.maidSyncher.domain.dsi.DSIRepository;
import pt.ist.maidSyncher.domain.exceptions.SyncActionError;
import pt.ist.maidSyncher.domain.exceptions.SyncEventIncogruenceBetweenOriginAndDestination;
import pt.ist.maidSyncher.domain.github.GHIssue;
import pt.ist.maidSyncher.domain.github.GHLabel;
import pt.ist.maidSyncher.domain.github.GHMilestone;
import pt.ist.maidSyncher.domain.github.GHObject;
import pt.ist.maidSyncher.domain.github.GHRepository;
import pt.ist.maidSyncher.domain.github.GHUser;
import pt.ist.maidSyncher.domain.sync.EmptySyncActionWrapper;
import pt.ist.maidSyncher.domain.sync.SyncActionWrapper;
import pt.ist.maidSyncher.domain.sync.SyncEvent;
import pt.ist.maidSyncher.domain.sync.logs.SyncWarningLog;
import pt.ist.maidSyncher.utils.MiscUtils;

public class ACTask extends ACTask_Base {

    public ACTask() {
        super();
    }

    private Collection<String> processMainAssignee(pt.ist.maidSyncher.api.activeCollab.ACTask acTask) {

        ACUser acUser = ACUser.findById(acTask.getAssigneeId());
        ACUser oldMainAssignee = getMainAssignee();
        setMainAssignee(acUser);
        return !ObjectUtils.equals(acUser, oldMainAssignee)
                ? Collections.singleton(getPropertyDescriptorNameAndCheckItExists(acTask, "assigneeId"))
                : Collections.EMPTY_SET;
    }

    private Collection<String> processOtherAssignees(pt.ist.maidSyncher.api.activeCollab.ACTask acTask) {

        boolean somethingChanged = false;
        Set<ACUser> newOtherAssigneesSet = new HashSet<ACUser>();

        Set<Long> otherAssigneesId = acTask.getOtherAssigneesId();

        for (Long userId : otherAssigneesId) {
            ACUser otherAssignee = ACUser.findById(userId);
            newOtherAssigneesSet.add(otherAssignee);
        }

        //retrieve the old
        HashSet<ACUser> oldSet = new HashSet<ACUser>(getOtherAssigneesSet());

        //now, let's compare
        if (!ObjectUtils.equals(newOtherAssigneesSet, oldSet)) {
            somethingChanged = true;
        }

        //now, let's substitute
        for (ACUser user : getOtherAssigneesSet()) {
            removeOtherAssignees(user);
        }

        for (ACUser user : newOtherAssigneesSet)
            addOtherAssignees(user);
        return somethingChanged
                ? Collections.singleton(getPropertyDescriptorNameAndCheckItExists(acTask, "otherAssigneesId"))
                : Collections.EMPTY_SET;
    }

    private Collection<String> processMilestone(pt.ist.maidSyncher.api.activeCollab.ACTask acTask) {
        ACMilestone newMilestone = ACMilestone.findById(acTask.getMilestoneId());
        ACMilestone oldMilestone = getMilestone();
        setMilestone(newMilestone);
        return !ObjectUtils.equals(oldMilestone, newMilestone)
                ? Collections.singleton(getPropertyDescriptorNameAndCheckItExists(acTask, "milestoneId"))
                : Collections.EMPTY_SET;

    }

    @Override
    public Collection<String> copyPropertiesFrom(Object orig)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Set<String> changedDescriptors = new HashSet<>(super.copyPropertiesFrom(orig));

        pt.ist.maidSyncher.api.activeCollab.ACTask acTask = (pt.ist.maidSyncher.api.activeCollab.ACTask) orig;
        //now let's take care of the milestone, main assignee and other assignees

        changedDescriptors.addAll(processMainAssignee(acTask));

        changedDescriptors.addAll(processOtherAssignees(acTask));

        changedDescriptors.addAll(processMilestone(acTask));

        changedDescriptors.addAll(processCategory(acTask));

        changedDescriptors.addAll(processLabel(acTask));

        changedDescriptors.addAll(processProject(acTask));

        return changedDescriptors;

    }

    private Collection<String> processProject(pt.ist.maidSyncher.api.activeCollab.ACTask acTask) {
        //let's get the projectid
        int projectId = acTask.getProjectId();
        checkArgument(projectId > 0);
        pt.ist.maidSyncher.domain.activeCollab.ACProject acCurrentProject = pt.ist.maidSyncher.domain.activeCollab.ACProject
                .findById(projectId);
        pt.ist.maidSyncher.domain.activeCollab.ACProject acOldProject = getProject();

        if (ObjectUtils.equals(acCurrentProject, acOldProject) == false) {
            setProject(acCurrentProject);
            return Collections.singleton(getPropertyDescriptorNameAndCheckItExists(acTask, DSC_PROJECT_ID));
        } else
            return Collections.emptySet();
    }

    public static ACTask findById(long id) {
        return (ACTask) MiscUtils.findACObjectsById(id, ACTask.class);
    }

    private Collection<String> processLabel(pt.ist.maidSyncher.api.activeCollab.ACTask acTask) {
        ACTaskLabel newTaskLabel = ACTaskLabel.findById(acTask.getLabelId());
        ACTaskLabel oldTaskLabel = getLabel();
        setLabel(newTaskLabel);
        return !ObjectUtils.equals(newTaskLabel, oldTaskLabel)
                ? Collections.singleton(getPropertyDescriptorNameAndCheckItExists(acTask, "labelId"))
                : Collections.EMPTY_SET;

    }

    private Collection<String> processCategory(pt.ist.maidSyncher.api.activeCollab.ACTask acTask) {
        ACTaskCategory newTaskCategory = ACTaskCategory.findById(acTask.getCategoryId());
        ACTaskCategory oldTaskCategory = getTaskCategory();
        setTaskCategory(newTaskCategory);
        return !ObjectUtils.equals(newTaskCategory, oldTaskCategory)
                ? Collections.singleton(getPropertyDescriptorNameAndCheckItExists(acTask, "categoryId"))
                : Collections.EMPTY_SET;
    }

    static ACTask process(pt.ist.maidSyncher.api.activeCollab.ACTask acTask) throws TaskNotVisibleException {
        return process(acTask, false);
    }

    static ACTask process(pt.ist.maidSyncher.api.activeCollab.ACTask acTask, boolean skipSync)
            throws TaskNotVisibleException {
        checkNotNull(acTask);
        //let's check on the visibility
        if (acTask.getVisibility() == false)
            throw new TaskNotVisibleException();
        return (ACTask) findOrCreateAndProccess(acTask, ACTask.class, MaidRoot.getInstance().getAcObjectsSet(),
                skipSync);
    }

    public static ACTask process(pt.ist.maidSyncher.api.activeCollab.ACTask task, ACProject project) {
        checkNotNull(project);

        pt.ist.maidSyncher.domain.activeCollab.ACProject acDomainProject = pt.ist.maidSyncher.domain.activeCollab.ACProject
                .process(project);

        ACTask acDomainTask;
        try {
            acDomainTask = process(task);
        } catch (TaskNotVisibleException e) {
            return null;
        }
        acDomainTask.setProject(acDomainProject);

        return acDomainTask;
    }

    public static ACTask process(pt.ist.maidSyncher.api.activeCollab.ACTask task, long projectId,
            boolean skipSync) {
        checkNotNull(task);

        pt.ist.maidSyncher.domain.activeCollab.ACProject acDomainProject = pt.ist.maidSyncher.domain.activeCollab.ACProject
                .findById(projectId);

        ACTask acDomainTask;
        try {
            acDomainTask = process(task, skipSync);
        } catch (TaskNotVisibleException e) {
            return null;
        }
        acDomainTask.setProject(acDomainProject);

        return acDomainTask;
    }

    public Set<ACUser> getAssignees() {
        HashSet<ACUser> toReturn = new HashSet<ACUser>();
        toReturn.addAll(getOtherAssigneesSet());
        toReturn.add(getMainAssignee());

        return toReturn;

    }

    @Override
    public DSIObject getDSIObject() {
        return getDsiObjectIssue();
    }

    @Override
    public DSIObject findOrCreateDSIObject() {
        DSIObject dsiObject = getDSIObject();
        if (dsiObject == null) {
            dsiObject = new DSIIssue();
            setDsiObjectIssue((DSIIssue) dsiObject);

        }
        return dsiObject;
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(ACTask.class);

    // List of descriptor names
    public static final String DSC_VISIBILITY = "visibility";
    public static final String DSC_BODY = "body";
    public static final String DSC_OTHER_ASSIGNEES_ID = "otherAssigneesId";
    public static final String DSC_ASSIGNEE_ID = "assigneeId";

    public static final String DSC_MILESTONE_ID = "milestoneId";
    public static final String DSC_CATEGORY_ID = "categoryId";
    public static final String DSC_PROJECT_ID = "projectId";

    private SyncActionWrapper syncCreateEvent(final Issue newGHIssue, final SyncEvent triggerEvent) {
        final Set<String> tickedDescriptors = new HashSet<>();
        for (String changedDescriptor : triggerEvent.getChangedPropertyDescriptorNames().getUnmodifiableList()) {
            tickedDescriptors.add(changedDescriptor);
            switch (changedDescriptor) {
            case DSC_PERMALINK:
            case DSC_OTHER_ASSIGNEES_ID:
            case DSC_ASSIGNEE_ID:
            case DSC_ID:
            case DSC_URL:
            case DSC_PROJECT_ID:
            case DSC_CATEGORY_ID:
                //the ones that we don't have to do anything
            case DSC_CREATED_ON:
            case DSC_UPDATED_ON:
            case DSC_PRIORITY:
            case DSC_UPDATED_BY_ID:
            case DSC_DUE_ON:
                //for now, let's do nothing with the id
                //of who created it
            case DSC_CREATED_BY_ID:
                break;
            case DSC_MILESTONE_ID:
                break;
            case DSC_NAME:
                break;
            case DSC_COMPLETE:
                break;
            case DSC_VISIBILITY:
                break;
            case DSC_BODY:
                break;
            default:
                tickedDescriptors.remove(changedDescriptor); //if we did not fall on any of the above
                //cases, let's remove it from the ticked descriptors

            }
        }

        SyncActionWrapper toReturnActionWrapper = new SyncActionWrapper<GHIssue>() {

            @Override
            public Set<SynchableObject> sync() throws SyncActionError {

                Set<SynchableObject> changedObjects = new HashSet<>();
                try {
                    //let's try to find out if we need to create a GHIssue (if we have an ACTaskCategory that
                    //has an DSIRepository associated, then we do)
                    ACTaskCategory acTaskCategory = getTaskCategory();
                    if (ACTaskCategory.hasGHSide(acTaskCategory) == false) {
                        return Collections.emptySet();
                    }

                    //now, let's get the repository
                    DSIRepository dsiRepository = (DSIRepository) acTaskCategory.getDSIObject();
                    GHRepository ghRepository = dsiRepository.getGitHubRepository();

                    //the label corresponds to the project name, let's try to retrieve it
                    final DSIProject dsiProject = (DSIProject) getProject().getDSIObject(); //depended

                    syncGHLabelFromACProject(getProject(), ghRepository, newGHIssue);

                    final GHUser repoOwner = ghRepository.getOwner();
                    //                LabelService labelService = new LabelService(MaidRoot.getGitHubClient());

                    GHObjectWrapper synchedGHMilestoneFromACMilestone = syncGHMilestoneFromACMilestone(
                            getMilestone(), ghRepository, newGHIssue);
                    if (synchedGHMilestoneFromACMilestone != null
                            && synchedGHMilestoneFromACMilestone.wasJustCreated) {
                        changedObjects.add(synchedGHMilestoneFromACMilestone.ghObject);
                    }

                    //TODO #16 - probably we will have to strip the html
                    newGHIssue.setBody(getBody());

                    newGHIssue.setTitle(getName());

                    //TODO assignee

                    //let's create the issue
                    IssueService issueService = new IssueService(MaidRoot.getGitHubClient());
                    Issue newlyCreatedIssue = issueService.createIssue(ghRepository, newGHIssue);

                    GHIssue ghProcess = GHIssue.process(newlyCreatedIssue, ghRepository, true);
                    changedObjects.add(ghProcess);

                    //we must add it to the other side of the DSIElement
                    DSIIssue dsiIssue = (DSIIssue) getDSIObject();
                    dsiIssue.setGhIssue(ghProcess);
                } catch (IOException ex) {
                    throw new SyncActionError(ex, changedObjects);
                }
                return changedObjects;
            }

            @Override
            public Collection<DSIObject> getSyncDependedDSIObjects() {
                Set<DSIObject> dsiObjectsDependedOn = new HashSet<>();
                dsiObjectsDependedOn.add(getProject().getDSIObject());
                if (getMilestone() != null)
                    dsiObjectsDependedOn.add(getMilestone().getDSIObject());
                return dsiObjectsDependedOn;
            }

            @Override
            public Collection<String> getPropertyDescriptorNamesTicked() {
                return tickedDescriptors;
            }

            @Override
            public SyncEvent getOriginatingSyncEvent() {
                return triggerEvent;
            }

            @Override
            public Set<Class> getSyncDependedTypesOfDSIObjects() {
                Set<Class> classesDependedOn = new HashSet<>();
                classesDependedOn.add(DSIProject.class);
                classesDependedOn.add(DSIMilestone.class);
                classesDependedOn.add(DSIRepository.class);
                return classesDependedOn;
            }
        };

        return toReturnActionWrapper;
    }

    private GHMilestone createSuitableGHMilestone(GHRepository ghRepository, ACMilestone acMilestone)
            throws IOException {
        MilestoneService milestoneService = new MilestoneService(MaidRoot.getGitHubClient());
        Milestone newMilestone = new Milestone();
        newMilestone.setTitle(acMilestone.getName());
        newMilestone.setDescription(acMilestone.getBody());
        newMilestone.setDueOn(acMilestone.getDueOn() != null ? acMilestone.getDueOn().toDate() : null);
        Milestone createdMilestone = milestoneService.createMilestone(ghRepository, newMilestone);
        GHMilestone processedGhMilestone = GHMilestone.process(createdMilestone, true);
        ghRepository.addMilestones(processedGhMilestone);
        return processedGhMilestone;
    }

    private GHMilestone tryToFindSuitableGHMilestone(GHRepository ghRepository, ACMilestone acMilestone) {
        for (GHMilestone ghMilestone : ghRepository.getMilestonesSet()) {
            if (ObjectUtils.equals(acMilestone.getName(), ghMilestone.getTitle()))
                return ghMilestone;
        }
        return null;
    }

    private GHLabel createSuitableGHLabel(GHRepository ghRepository,
            pt.ist.maidSyncher.domain.activeCollab.ACProject project) throws IOException {
        LabelService labelService = new LabelService(MaidRoot.getInstance().getGitHubClient());
        Label newLabel = new Label();
        newLabel.setName(GHLabel.PROJECT_PREFIX + project.getName());
        Label createdLabel = labelService.createLabel(ghRepository, newLabel);
        return GHLabel.process(createdLabel, ghRepository.getId(), true);
    }

    /**
     * 
     * @param ghRepository
     * @param project
     * @return a GHLabel that is suitable (i.e. its name matches the one needed for the given project), if any was found,
     *         or null otherwise
     */
    private GHLabel tryToFindSuitableGHLabel(GHRepository ghRepository,
            pt.ist.maidSyncher.domain.activeCollab.ACProject project) {
        for (GHLabel ghLabel : ghRepository.getLabelsDefinedSet()) {
            if ((GHLabel.PROJECT_PREFIX + project.getName()).equalsIgnoreCase(ghLabel.getName())) {
                return ghLabel;
            }
        }
        return null;
    }

    private SyncActionWrapper syncUpdateEvent(final SyncEvent triggerEvent) {
        final Set<String> tickedDescriptors = new HashSet<>();
        for (String changedDescriptor : triggerEvent.getChangedPropertyDescriptorNames().getUnmodifiableList()) {
            tickedDescriptors.add(changedDescriptor);
            switch (changedDescriptor) {

            case DSC_OTHER_ASSIGNEES_ID:
                //let us just ignore the other assignees for now
                break;
            case DSC_COMPLETE:
                break;
            case DSC_ASSIGNEE_ID:
                //for now let's ignore the assignee TODO
                break;
            case DSC_URL:
                break;
            case DSC_ID:
                //that shouldn't have happened!!
                new SyncWarningLog(MaidRoot.getInstance().getCurrentSyncLog(),
                        "Id of ACTask changed!!. It shouldn't have happened, oh well. SyncEvent: " + triggerEvent);
                break;
            //the ones that we don't have to do anything
            case DSC_PERMALINK:
            case DSC_CREATED_ON:
            case DSC_UPDATED_ON:
            case DSC_PRIORITY:
            case DSC_UPDATED_BY_ID:
            case DSC_DUE_ON:
                break;
            //for now, let's do nothing with the id
            //of who created it
            case DSC_CREATED_BY_ID:
                break;
            case DSC_NAME:
                break;
            case DSC_VISIBILITY:
                //if the visibility is concealed, we wouldn't reach here, let's just make sure that's so
                if (getVisibility() == false)
                    throw new SyncActionError("We are trying to sync a Task that isn't visible");
                break;
            case DSC_BODY:
                //TODO #16 probably strip the HTML from the getBody
                break;
            case DSC_PROJECT_ID:
                //the project changed, thus we must change the label from the GH side
                //but let's do it on the sync (giving a chance for the Labels and etc
                //to be already synched)

                break;
            case DSC_MILESTONE_ID:
                break;
            case DSC_CATEGORY_ID:
                break;

            default:
                tickedDescriptors.remove(changedDescriptor); //if we did not fall on any of the above
                //cases, let's remove it from the ticked descriptors

            }
        }

        final boolean completeChanged = tickedDescriptors.contains(DSC_COMPLETE);
        final boolean projectChanged = tickedDescriptors.contains(DSC_PROJECT_ID);
        final boolean milestoneChanged = tickedDescriptors.contains(DSC_MILESTONE_ID);
        final boolean taskCategoryChanged = tickedDescriptors.contains(DSC_CATEGORY_ID);
        final boolean taskNameChanged = tickedDescriptors.contains(DSC_NAME);

        final boolean taskBodyChanged = tickedDescriptors.contains(DSC_BODY);

        return new SyncActionWrapper() {

            @Override
            public Set<SynchableObject> sync() throws SyncActionError {
                Issue ghIssueToUpdate = null;
                DSIIssue dsiIssue = (DSIIssue) getDSIObject();
                GHIssue ghIssue = dsiIssue.getGhIssue();

                Set<SynchableObject> changedObjects = new HashSet<SynchableObject>();

                try {

                    //if the ACTaskCategory has no GHSide, let's do nothing here
                    if (ACTaskCategory.hasGHSide(getTaskCategory()) == false) {
                        //let's just not do anything in this case, as this might have been a mistake
                        return Collections.emptySet();
                    }

                    ACTaskCategory taskCategory = getTaskCategory();
                    GHRepository newGHRepository = null;
                    //setting the newGHRepository
                    if (taskCategory != null) {
                        DSIRepository dsiRepository = (DSIRepository) taskCategory.getDSIObject();
                        newGHRepository = dsiRepository.getGitHubRepository();
                    } else {
                        if (getProject().getDsiRepositoryFromDefaultProject() != null) {
                            newGHRepository = getProject().getDsiRepositoryFromDefaultProject()
                                    .getGitHubRepository();
                        } else {
                            newGHRepository = null;
                        }
                    }

                    GHRepository oldGHRepository = getDSIObject() == null
                            || ((DSIIssue) getDSIObject()).getGhIssue() == null ? null
                                    : ((DSIIssue) getDSIObject()).getGhIssue().getRepository();
                    if ((taskCategoryChanged && ACTaskCategory.hasGHSide(getTaskCategory()))
                            && !ObjectUtils.equals(newGHRepository, oldGHRepository)) {

                        //as the overriden method of the copyPropertiesTo
                        //already takes care of the labels

                        Issue ghOldIssueToUpdate = null;
                        Issue newIssue = null;
                        if (ghIssue != null) {
                            ghOldIssueToUpdate = ghIssue.getNewPrefilledIssue(null);

                        }
                        newIssue = prefillIssue(newGHRepository, changedObjects);

                        GHIssue newGhCreatedIssue = updateOrCreateIssueIfNotNull(newGHRepository, changedObjects,
                                ghOldIssueToUpdate == null, newIssue);

                        dsiIssue.setGhIssue(newGhCreatedIssue);

                        //if we have an old issue, let's mark it as moved &
                        //do all the needed changes
                        if (ghOldIssueToUpdate != null) {
                            //adding the deleted label to the old issue
                            Label labelDeleted = new Label();
                            labelDeleted.setName(GHLabel.DELETED_LABEL_NAME);

                            List<Label> labelsToUseInTheOldIssue = addLabelsToUse(ghOldIssueToUpdate.getLabels(),
                                    labelDeleted);
                            ghOldIssueToUpdate.setLabels(labelsToUseInTheOldIssue);

                            //let's close the old issue
                            ghOldIssueToUpdate.setState(GHIssue.STATE_CLOSED);

                            //and add into the description what happened to it
                            String oldIssueBody = ghOldIssueToUpdate.getBody();
                            String newBodyForIssue = applyMovedTo(oldIssueBody, newGhCreatedIssue);
                            ghOldIssueToUpdate.setBody(newBodyForIssue);

                            //let's 'commit' the old issue changes

                            IssueService issueService = new IssueService(MaidRoot.getGitHubClient());
                            GHIssue oldGHIssue = GHIssue.process(
                                    issueService.editIssue(oldGHRepository, ghOldIssueToUpdate), oldGHRepository,
                                    true);
                            changedObjects.add(oldGHIssue);
                        }
                        //now let's do the same for each subtask
                        for (ACSubTask acSubTask : getSubTasksSet()) {
                            //let's migrate this
                            if (acSubTask.getDsiObjectSubTask().getParentIssue().getGhIssue() == null)
                                acSubTask.getDsiObjectSubTask().getParentIssue().setGhIssue(newGhCreatedIssue);
                            processGHIssueFromACSubTaskMigration(newGHRepository, acSubTask, changedObjects);
                        }

                    } else {
                        updateIssueSimpleFieldsIfNeccessary(ghIssueToUpdate, ghIssue, oldGHRepository,
                                changedObjects, false);

                    }

                    //                //extra check
                    //                if (ObjectUtils.equals(processedGHIssue, ((DSIIssue) getDSIObject()).getGhIssue()) == false)
                    //                    throw new SyncActionError("we did an update and the resulting GHIssue don't match");

                } catch (IOException ex) {
                    throw new SyncActionError(ex, changedObjects);
                }

                return changedObjects;
            }

            private GHIssue updateIssueSimpleFieldsIfNeccessary(Issue ghIssueToUpdate, GHIssue ghIssue,
                    GHRepository ghRepository, Set<SynchableObject> changedObjects, boolean createInsteadOfEdit)
                    throws IOException {
                Issue issue = ghIssueToUpdate;
                if (ghIssue != null) {
                    if (taskNameChanged) {
                        issue = ghIssue.getNewPrefilledIssue(issue);
                        issue.setTitle(getName());
                    }
                    if (taskBodyChanged) {
                        issue = ghIssue.getNewPrefilledIssue(issue);
                        issue.setBody(getBody());
                    }
                    if (completeChanged) {
                        issue = ghIssue.getNewPrefilledIssue(issue);
                        //let's make the task as completed, or not
                        if (getComplete()) {
                            issue.setState(GHIssue.STATE_CLOSED);
                        } else {
                            issue.setState(GHIssue.STATE_OPEN);
                        }
                    }

                    if (projectChanged) {
                        //let's try to find the GHLabel associated with this one
                        //and if it doesn't exist, create it
                        issue = ghIssue.getNewPrefilledIssue(issue);

                        syncGHLabelFromACProject(getProject(), ghRepository, issue);

                    }
                    if (milestoneChanged) {

                        issue = ghIssue.getNewPrefilledIssue(issue);
                        syncGHMilestoneFromACMilestone(getMilestone(), ghRepository, issue);
                    }
                }

                return updateOrCreateIssueIfNotNull(ghRepository, changedObjects, createInsteadOfEdit, issue);
            }

            private GHIssue updateOrCreateIssueIfNotNull(GHRepository ghRepository,
                    Set<SynchableObject> changedObjects, boolean createInsteadOfEdit, Issue issueToCreate)
                    throws IOException {
                //let's edit the issue, if we have to
                if (issueToCreate != null) {

                    IssueService issueService = new IssueService(MaidRoot.getGitHubClient());
                    Issue changedIssue = null;
                    if (createInsteadOfEdit) {
                        changedIssue = issueService.createIssue(ghRepository, issueToCreate);
                    } else {
                        changedIssue = issueService.editIssue(ghRepository, issueToCreate);

                    }
                    GHIssue processedGHIssue = GHIssue.process(changedIssue, ghRepository, true);
                    changedObjects.add(processedGHIssue);
                    return processedGHIssue;
                }
                return null;
            }

            private void processGHIssueFromACSubTaskMigration(GHRepository newGHRepository, ACSubTask acSubTask,
                    Set<SynchableObject> changedObjects) throws IOException {
                //let's get the old issue based on the current one
                Issue oldIssue = new Issue();

                //let's create the new blank Issue
                Issue newIssue = new Issue();

                GHIssue subTaskGHIssue = null;

                //copy everything from the old one, if it exists
                if (acSubTask.getDsiObjectSubTask() != null
                        && acSubTask.getDsiObjectSubTask().getGhIssue() != null) {
                    subTaskGHIssue = acSubTask.getDsiObjectSubTask().getGhIssue();
                    try {
                        subTaskGHIssue.copyPropertiesTo(newIssue);
                        subTaskGHIssue.copyPropertiesTo(oldIssue);
                    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
                            | TaskNotVisibleException e) {
                        throw new SyncActionError(
                                "Error trying to prefill the properties from the GHIssue to the bean.", e);
                    }
                } else {
                    //the old one doesn't exist, let's create the GHSide
                    acSubTask.createGHIssueForSubTask(changedObjects, (ACTask) triggerEvent.getOriginObject(),
                            getTaskCategory());
                    return;
                }

                //taking care of the eventual milestone
                GHObjectWrapper syncGHMilestoneFromACMilestone = syncGHMilestoneFromACMilestone(getMilestone(),
                        newGHRepository, newIssue);

                IssueService issueService = new IssueService(MaidRoot.getGitHubClient());
                GHIssue newGhCreatedIssue = GHIssue.process(issueService.createIssue(newGHRepository, newIssue),
                        newGHRepository, true);

                DSIIssue dsiIssue = (DSIIssue) getDSIObject();
                dsiIssue.setGhIssue(newGhCreatedIssue);

                //adding the deleted label to the old issue
                Label labelDeleted = new Label();
                labelDeleted.setName(GHLabel.DELETED_LABEL_NAME);

                List<Label> labelsToUseInTheOldIssue = addLabelsToUse(oldIssue.getLabels(), labelDeleted);
                oldIssue.setLabels(labelsToUseInTheOldIssue);

                //let's close the old issue
                oldIssue.setState(GHIssue.STATE_CLOSED);

                //and add into the description what happened to it
                String oldIssueBody = oldIssue.getBody();
                String newBodyForIssue = applyMovedTo(oldIssueBody, newGhCreatedIssue);
                oldIssue.setBody(newBodyForIssue);
                //let's close the oldIssue
                changedObjects.add(GHIssue.process(issueService.editIssue(subTaskGHIssue.getRepository(), oldIssue),
                        subTaskGHIssue.getRepository(), true));
                return;

            }

            @Override
            public Collection<DSIObject> getSyncDependedDSIObjects() {
                return Collections.EMPTY_LIST;
            }

            @Override
            public Collection getPropertyDescriptorNamesTicked() {
                return tickedDescriptors;
            }

            @Override
            public SyncEvent getOriginatingSyncEvent() {
                return triggerEvent;
            }

            @Override
            public Set<Class> getSyncDependedTypesOfDSIObjects() {
                Set<Class> dependedOnObjects = new HashSet();
                dependedOnObjects.add(ACTaskCategory.class);
                dependedOnObjects.add(ACProject.class);
                return dependedOnObjects;
            }
        };

    }

    static class GHObjectWrapper {
        public final GHObject ghObject;
        public final boolean wasJustCreated;

        public GHObjectWrapper(GHObject ghObject, boolean wasRecentlyCreated) {
            this.ghObject = ghObject;
            this.wasJustCreated = wasRecentlyCreated;
        }
    }

    private List<Label> addLabelsToUse(List<Label> currentlyUsedLabels, Label... labelsToAdd) {
        List<Label> labelsToReturn = null;
        if (currentlyUsedLabels == null) {
            labelsToReturn = new ArrayList<Label>();
        } else {
            labelsToReturn = currentlyUsedLabels;
        }
        for (Label labelToAdd : labelsToAdd) {
            labelsToReturn.add(labelToAdd);
        }
        return labelsToReturn;
    }

    GHObjectWrapper syncGHLabelFromACProject(pt.ist.maidSyncher.domain.activeCollab.ACProject acProject,
            GHRepository ghRepository, Issue issueToUpdate) throws IOException {

        boolean wasJustCreated = false;
        DSIProject dsiProject = (DSIProject) acProject.getDSIObject();
        GHLabel gitHubLabel = dsiProject.getGitHubLabelFor(ghRepository);
        if (gitHubLabel == null) {
            gitHubLabel = tryToFindSuitableGHLabel(ghRepository, acProject);
            if (gitHubLabel == null) {
                gitHubLabel = createSuitableGHLabel(ghRepository, acProject);
                wasJustCreated = true;

            }
        }

        Label newLabel = new Label();
        newLabel.setName(gitHubLabel.getName());
        List<Label> labelsToUse = addLabelsToUse(issueToUpdate.getLabels(), newLabel);

        issueToUpdate.setLabels(labelsToUse);

        return new GHObjectWrapper(gitHubLabel, wasJustCreated);

    }

    /**
     * 
     * @param acMilestone the acMilestone to process
     * @param ghRepository the repository where to place the new Milestone
     * @param ghIssueToUpdate the {@link Issue} object to edit/create
     * @return a {@link GHMilestoneWrapper} with the {@link GHMilestone} reused/created
     *         creates/reuses a milestone that it might find on the given repository, wich is returned in
     *         a {@link GHMilestoneWrapper}, and updates it on the given ghIssueToUpdate
     * 
     * @throws IOException if something went wrong with the creation of a new Milestone
     *             Creates or reuses a GHMilestone if there is an associated acMilestone
     */
    GHObjectWrapper syncGHMilestoneFromACMilestone(ACMilestone acMilestone, GHRepository ghRepository,
            Issue ghIssueToUpdate) throws IOException {
        if (acMilestone != null) {
            checkNotNull(ghIssueToUpdate);
            checkNotNull(ghRepository);
            boolean wasCreatedANewMilestone = false;

            final DSIMilestone dsiMilestone = (DSIMilestone) acMilestone.getDSIObject(); //depended upon
            GHMilestone ghMilestone = dsiMilestone.getGhMilestone(ghRepository);
            if (ghMilestone == null) {
                //we must reuse/create it
                ghMilestone = tryToFindSuitableGHMilestone(ghRepository, acMilestone);
                if (ghMilestone == null) {
                    ghMilestone = createSuitableGHMilestone(ghRepository, acMilestone);
                    wasCreatedANewMilestone = true;
                }
            }

            //we don't need the milestoneService, just the number
            Milestone milestone = new Milestone();
            milestone.setNumber(ghMilestone.getNumber());
            ghIssueToUpdate.setMilestone(milestone);

            return new GHObjectWrapper(ghMilestone, wasCreatedANewMilestone);
        }
        return null;

    }

    String applyMovedTo(String oldIssueBody, GHIssue newGhCreatedIssue) {
        String newString = null;
        if (oldIssueBody == null) {
            newString = "";
        } else {
            newString = oldIssueBody;
        }
        return GHIssue.MOVED_TO_PREFIX + newGhCreatedIssue.getRepository().generateId() + "#"
                + newGhCreatedIssue.getNumber() + newString;

    }

    Issue prefillIssue(GHRepository targetRepository, Set<SynchableObject> changedObjects) {
        Issue issue = new Issue();
        issue.setBody(getBody());
        GHLabel gitHubLabelForProject = ((DSIProject) getProject().getDSIObject())
                .getGitHubLabelFor(targetRepository);
        if (gitHubLabelForProject != null) {
            Label label = new Label();
            try {
                gitHubLabelForProject.copyPropertiesTo(label);
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
                    | TaskNotVisibleException e) {
                throw new SyncActionError(e);
            }
            List<Label> singletonLabels = Collections.singletonList(label);
            issue.setLabels(singletonLabels);
        }
        if (getComplete() == null) {
            issue.setState(GHIssue.STATE_OPEN);
        } else {
            issue.setState(getComplete() ? GHIssue.STATE_CLOSED : GHIssue.STATE_OPEN);
        }

        issue.setTitle(getName());

        try {
            GHObjectWrapper synchedGHMilestoneFromACMilestone = syncGHMilestoneFromACMilestone(getMilestone(),
                    targetRepository, issue);
            if (synchedGHMilestoneFromACMilestone != null && synchedGHMilestoneFromACMilestone.wasJustCreated)
                changedObjects.add(synchedGHMilestoneFromACMilestone.ghObject);
        } catch (IOException e) {
            throw new SyncActionError(e);
        }

        return issue;
    }

    @Override
    public SyncActionWrapper sync(final SyncEvent syncEvent) {
        SyncActionWrapper syncActionWrapperToReturn = null;
        if (syncEvent.getTypeOfChangeEvent().equals(SyncEvent.TypeOfChangeEvent.CREATE)) {
            //then we should need to create a GHIssue, let's just make sure that's correct
            if (syncEvent.getTargetSyncUniverse().equals(SyncEvent.SyncUniverse.ACTIVE_COLLAB))
                throw new SyncEventIncogruenceBetweenOriginAndDestination("For syncEvent: " + syncEvent.toString());
            Issue newGhIssueToCreate = new Issue();
            syncActionWrapperToReturn = syncCreateEvent(newGhIssueToCreate, syncEvent);

        } else if (syncEvent.getTypeOfChangeEvent().equals(SyncEvent.TypeOfChangeEvent.UPDATE)) {
            //let's retrieve the existing GHIssue and use it to prefill the Issue
            syncActionWrapperToReturn = syncUpdateEvent(syncEvent);

        } else {
            LOGGER.warn("Read and Delete events not supported yet. " + syncEvent);
            syncActionWrapperToReturn = new EmptySyncActionWrapper(syncEvent);
        }

        return syncActionWrapperToReturn;
    }

}