pt.ist.maidSyncher.domain.github.GHIssue.java Source code

Java tutorial

Introduction

Here is the source code for pt.ist.maidSyncher.domain.github.GHIssue.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.github;

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 jvstm.cps.ConsistencyPredicate;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
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.Repository;
import org.eclipse.egit.github.core.User;
import org.eclipse.egit.github.core.service.IssueService;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pt.ist.maidSyncher.api.activeCollab.ACMilestone;
import pt.ist.maidSyncher.api.activeCollab.ACSubTask;
import pt.ist.maidSyncher.api.activeCollab.ACTask;
import pt.ist.maidSyncher.domain.MaidRoot;
import pt.ist.maidSyncher.domain.SynchableObject;
import pt.ist.maidSyncher.domain.activeCollab.ACProject;
import pt.ist.maidSyncher.domain.activeCollab.ACTaskCategory;
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.dsi.DSISubTask;
import pt.ist.maidSyncher.domain.exceptions.SyncActionError;
import pt.ist.maidSyncher.domain.exceptions.SyncEventIncogruenceBetweenOriginAndDestination;
import pt.ist.maidSyncher.domain.sync.EmptySyncActionWrapper;
import pt.ist.maidSyncher.domain.sync.SyncActionWrapper;
import pt.ist.maidSyncher.domain.sync.SyncEvent;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;

public class GHIssue extends GHIssue_Base {
    private static final Logger LOGGER = LoggerFactory.getLogger(GHIssue.class);

    public final static String STATE_CLOSED = "closed";
    public final static String STATE_OPEN = "open";

    //The PropertyDescriptor s of this issue
    public final static String DSC_STATE = "state";
    public final static String DSC_LABELS = "labels";
    public final static String DSC_NUMBER = "number";
    public final static String DSC_CLOSED_AT = "closedAt";
    public final static String DSC_BODY = "body";
    public final static String DSC_TITLE = "title";
    public final static String DSC_UPDATED_AT = "updatedAt";
    public final static String DSC_HTML_URL = "htmlUrl";
    public final static String DSC_MILESTONE = "milestone";

    public final static String MOVED_TO_PREFIX = "Moved to ";

    public GHIssue() {
        super();
        MaidRoot.getInstance().addGhIssues(this);
    }

    static GHIssue process(Issue issue) {
        return process(issue, false);
    }

    static GHIssue process(Issue issue, boolean skipSync) {
        checkNotNull(issue);
        MaidRoot maidRoot = MaidRoot.getInstance();
        return (GHIssue) findOrCreateAndProccess(issue, GHIssue.class, maidRoot.getGhIssuesSet(), skipSync);
    }

    public static GHIssue process(Issue issue, Repository repository) {
        return process(issue, repository, false);

    }

    public static GHIssue process(Issue issue, Repository repository, boolean skipSync) {
        checkNotNull(repository);
        //let's first take care of the issue, and then assign it the repository
        GHIssue ghIssue = process(issue, skipSync);
        GHRepository ghRepository = GHRepository.process(repository, skipSync);

        ghIssue.setRepository(ghRepository);

        return ghIssue;

    }

    public static GHIssue process(Issue issue, GHRepository ghRepository, boolean skipSync) {
        checkNotNull(ghRepository);
        //let's first take care of the issue, and then assign it the repository
        GHIssue ghIssue = process(issue, skipSync);

        ghIssue.setRepository(ghRepository);

        return ghIssue;

    }

    protected static 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;
    }

    @Override
    public void copyPropertiesTo(Object dest) throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException, TaskNotVisibleException {
        super.copyPropertiesTo(dest);

        //let's take care of the non simple properties i.e., Milestone and Labels
        Issue issue = (Issue) dest;

        if (getMilestone() != null) {
            //let's get it to the issue
            GHMilestone ghMilestone = getMilestone();
            Milestone newMilestone = new Milestone();
            //we only need the number in reality
            newMilestone.setNumber(ghMilestone.getNumber());
            issue.setMilestone(newMilestone);
        }

        List<Label> labelsToBeCopied = new ArrayList<>();
        for (GHLabel ghLabel : getLabelsSet()) {
            Label newLabel = new Label();
            ghLabel.copyPropertiesTo(newLabel);
            labelsToBeCopied.add(newLabel);
        }

        if (labelsToBeCopied.isEmpty() == false)
            //because there is a different behaviour
            //if the issue.getLabels returns null
            //or an empty list
            issue.setLabels(labelsToBeCopied);

    }

    @Override
    public Collection<String> copyPropertiesFrom(Object orig)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        checkNotNull(orig);
        checkArgument(orig instanceof Issue, "provided object must be an instance of " + Issue.class.getName());

        Set<String> changedPropertyDescriptors = new HashSet<>(super.copyPropertiesFrom(orig));

        Issue issue = (Issue) orig;
        //now let's take care of the relations with the other objects
        //like Milestone and Label
        Milestone milestone = issue.getMilestone();
        if (milestone != null) {
            GHMilestone ghMilestone = GHMilestone.process(milestone);
            if (!ObjectUtils.equals(getMilestone(), ghMilestone))
                changedPropertyDescriptors.add(getPropertyDescriptorNameAndCheckItExists(issue, "milestone"));
            setMilestone(ghMilestone);
        }

        Set<GHLabel> ghOldLabels = new HashSet<GHLabel>(getLabelsSet());
        Set<GHLabel> newGHLabels = new HashSet<GHLabel>();
        if (issue.getLabels() != null) {
            for (Label label : issue.getLabels()) {
                GHLabel ghLabel = GHLabel.process(label);
                newGHLabels.add(ghLabel);
            }
            if (!ObjectUtils.equals(ghOldLabels, newGHLabels))
                changedPropertyDescriptors.add(getPropertyDescriptorNameAndCheckItExists(issue, "labels"));
            for (GHLabel ghLabel : getLabelsSet()) {
                removeLabels(ghLabel);
            }
            for (GHLabel ghLabel : newGHLabels) {
                addLabels(ghLabel);
            }
        }

        User assignee = issue.getAssignee();
        GHUser ghNewAssignee = null;
        if (assignee != null)
            ghNewAssignee = GHUser.process(assignee);
        GHUser ghOldAssignee = getAssignee();
        if (!ObjectUtils.equals(ghNewAssignee, ghOldAssignee))
            changedPropertyDescriptors.add(getPropertyDescriptorNameAndCheckItExists(issue, "assignee"));
        setAssignee(ghNewAssignee);
        return changedPropertyDescriptors;
    }

    @Override
    public DateTime getUpdatedAtDate() {
        return getUpdatedAt() == null ? getCreatedAt() : getUpdatedAt();
    }

    @ConsistencyPredicate
    private boolean checkDSIObjectMultiplicity() {
        //we cannot be both an issue and a subtask
        if (getDsiObjectIssue() != null && getDsiObjectSubTask() != null)

            return false;
        return true;
    }

    @Override
    public DSIObject getDSIObject() {
        if (getDsiObjectIssue() != null)
            return getDsiObjectIssue();
        else
            return getDsiObjectSubTask();
    }

    @Override
    public DSIObject findOrCreateDSIObject() {
        //let's either make a DSISubTask or a DSIIssue
        //depending if we have a @subtask on the description or not
        DSIObject dsiObjectToReturn = getDSIObject();
        if (dsiObjectToReturn == null) {
            //we cannot create ACSubTasks through GH without using a comment on an
            //already existing issue, so, we assume it's a Issue
            dsiObjectToReturn = new DSIIssue();
            setDsiObjectIssue((DSIIssue) dsiObjectToReturn);
        }
        return dsiObjectToReturn;
    }

    @Override
    public SyncActionWrapper sync(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.GITHUB))
                throw new SyncEventIncogruenceBetweenOriginAndDestination("For syncEvent: " + syncEvent.toString());

            //if we are creating an artefact on AC side, it mustn't be a SubTask, it's a Task
            ACTask newAcTask = new ACTask();
            syncActionWrapperToReturn = syncCreateEvent(newAcTask, syncEvent);

        } else if (syncEvent.getTypeOfChangeEvent().equals(SyncEvent.TypeOfChangeEvent.UPDATE)) {
            //let's retrieve the existing ACTask or ACSubTask and use it to prefill the api object
            DSIObject dsiIssueOrSubTask = getDSIObject();
            if (dsiIssueOrSubTask instanceof DSIIssue) {
                DSIIssue dsiIssue = (DSIIssue) dsiIssueOrSubTask;
                pt.ist.maidSyncher.domain.activeCollab.ACTask acDomainTask = dsiIssue.getAcTask();
                ACTask newAcTask = new ACTask();
                try {
                    acDomainTask.copyPropertiesTo(newAcTask);
                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
                        | TaskNotVisibleException e) {
                    throw new SyncActionError("Error trying to prefill the properties from the ACTask to the bean.",
                            syncEvent, e);
                }

                syncActionWrapperToReturn = syncUpdateTaskEvent(newAcTask, dsiIssue, syncEvent);

            } else if (dsiIssueOrSubTask instanceof DSISubTask) {
                DSISubTask dsiSubTask = (DSISubTask) dsiIssueOrSubTask;
                pt.ist.maidSyncher.domain.activeCollab.ACSubTask acDomainSubTask = dsiSubTask.getAcSubTask();
                ACSubTask newAcSubTask = new ACSubTask();
                try {
                    acDomainSubTask.copyPropertiesTo(newAcSubTask);
                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
                        | TaskNotVisibleException e) {
                    throw new SyncActionError(
                            "Error trying to prefill the properties from the ACSubTask to the bean.", syncEvent, e);
                }

                syncActionWrapperToReturn = syncUpdateSubTaskEvent(newAcSubTask, dsiSubTask, syncEvent);

            }
        }

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

        return syncActionWrapperToReturn;
    }

    /**
     * Enforces only one Label that corresponds to an ACProject.
     * <strong>Makes changes to the GH repository</strong>
     * 
     * @throws IOException
     * @{@link Deprecated} - not the behavior used anymore
     */
    @Deprecated
    private void validateAndCorrectLabels() throws IOException {
        GHLabel labelFound = null;
        Set<GHLabel> labelsToRemove = new HashSet<>();
        for (GHLabel ghLabel : getLabelsSet()) {
            if (ghLabel.getDSIObject() != null) {
                if (labelFound != null) {
                    labelsToRemove.add(ghLabel);
                } else {
                    labelFound = ghLabel;
                }
            }
        }

        //let us enforce the labels then
        IssueService issueService = new IssueService(MaidRoot.getGitHubClient());
        Issue issueToRemoveLabelsFrom = issueService.getIssue(getRepository(), getNumber());
        for (Label currentLabel : issueToRemoveLabelsFrom.getLabels()) {
            final String currentLabelName = currentLabel.getName();
            boolean removeThisOne = Iterables.any(labelsToRemove, new Predicate<GHLabel>() {
                @Override
                public boolean apply(GHLabel input) {
                    if (input == null)
                        return false;
                    return input.getName().equalsIgnoreCase(currentLabelName);

                }

            });
            if (removeThisOne) {
                issueToRemoveLabelsFrom.getLabels().remove(currentLabel);
            }
        }

        //let us communicate the changes
        Issue newlyEditedIssue = issueService.editIssue(getRepository(), issueToRemoveLabelsFrom);
        try {
            copyPropertiesFrom(newlyEditedIssue);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new Error(e);
        }

    }

    private SyncActionWrapper syncUpdateSubTaskEvent(ACSubTask newAcSubTask, DSISubTask dsiSubTask,
            final SyncEvent syncEvent) {
        final Set<String> tickedDescriptors = new HashSet<>();
        boolean auxChangedLabels = false;
        boolean auxChangedState = false;
        boolean auxChangedBody = false;
        boolean auxChangedTitle = false;
        for (String changedDescriptor : syncEvent.getChangedPropertyDescriptorNames().getUnmodifiableList()) {
            tickedDescriptors.add(changedDescriptor);
            switch (changedDescriptor) {
            case DSC_ID:
            case DSC_URL:
            case DSC_HTML_URL:
            case DSC_CREATED_AT:
            case DSC_UPDATED_AT:
            case DSC_NUMBER:
            case DSC_MILESTONE:
            case DSC_CLOSED_AT:
                //changes to this milestone don't reflect
                //on the other side, because we are only a subtask
                break; //the ones above, there'se no sense in changing anything
            case DSC_LABELS:
                //seen that we were a subtask, if we edited the project label,
                //we need to create an ACTask on another ACProject
                auxChangedLabels = true;
                break;
            case DSC_STATE:
                //we might have to close/open the Task/SubTask on the other side
                auxChangedState = true;
                break;
            case DSC_BODY:
                //let us change the body on the other side as well
                auxChangedBody = true;
                break;
            case DSC_TITLE:
                //change the name on the other side
                auxChangedTitle = true;
                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 changedLabels = auxChangedLabels;
        final boolean changedState = auxChangedState;
        final boolean changedBody = auxChangedBody;
        final boolean changedTitle = auxChangedTitle;

        return new SyncActionWrapper<SynchableObject>() {

            @Override
            public Set<SynchableObject> sync() throws SyncActionError {
                Issue newIssue = null;
                ACSubTask acSubTaskToEdit = null;

                Set<SynchableObject> changedObjects = new HashSet<>();
                try {
                    if (changedLabels) {
                        //we do nothing here, we only sync if it's the parent that changed
                    }
                    if (changedState) {
                        //then we need to apply the state to the corresponding ACSubTask
                        acSubTaskToEdit = getNewPrefilledACSubTask(acSubTaskToEdit);
                        switch (getState()) {
                        case STATE_CLOSED:
                            acSubTaskToEdit.setComplete(true);
                            break;
                        case STATE_OPEN:
                            acSubTaskToEdit.setComplete(false);
                            break;
                        }

                    }
                    if (changedBody) {
                        //we are a subtask, we must ensure that we have the relation to the task in the beginning
                        if (StringUtils.startsWith(getBody(), getSubTaskBodyPrefix()) == false) {
                            //then we need to correct that!!
                            String newBody = applySubTaskBodyPrefix(getBody());
                            newIssue = getNewPrefilledIssue(newIssue);
                            newIssue.setBody(newBody);
                        }

                    }
                    if (changedTitle) {
                        //let's update the title on the other end
                        acSubTaskToEdit = getNewPrefilledACSubTask(acSubTaskToEdit);
                        acSubTaskToEdit.setName(getTitle());
                    }

                    if (newIssue != null) {
                        //we should apply the changes
                        IssueService issueService = new IssueService(MaidRoot.getGitHubClient());
                        Issue editedIssue = issueService.editIssue(getRepository(), newIssue);
                        changedObjects.add(GHIssue.process(editedIssue, true));

                    }

                    if (acSubTaskToEdit != null) {
                        //we should apply the changes
                        String url = getDsiObjectSubTask().getAcSubTask().getUrl();
                        ACSubTask newlyUpdatedSubTask = acSubTaskToEdit.update(url);
                        try {
                            getDsiObjectSubTask().getAcSubTask().copyPropertiesFrom(newlyUpdatedSubTask);
                            changedObjects.add(getDsiObjectSubTask().getAcSubTask());
                        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
                                | TaskNotVisibleException e) {
                            throw new SyncActionError(e, changedObjects);
                        }
                    }
                } catch (IOException ex) {
                    throw new SyncActionError(ex, changedObjects);
                }

                return changedObjects;
            }

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

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

            @Override
            public Set<Class> getSyncDependedTypesOfDSIObjects() {
                Set<Class> classesDependedOn = new HashSet<>();
                classesDependedOn.add(GHLabel.class);
                classesDependedOn.add(pt.ist.maidSyncher.domain.activeCollab.ACSubTask.class);
                return classesDependedOn;
            }

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

    public final static String SUB_TASK_BODY_PREFIX = "Subtask of #";

    private String getSubTaskBodyPrefix() {
        DSISubTask dsiSubTask = (DSISubTask) getDSIObject();
        return SUB_TASK_BODY_PREFIX + dsiSubTask.getParentIssue().getGhIssue().getNumber();
    }

    private static String getSubTaskBodyPrefix(GHIssue ghParentIssue) {
        return SUB_TASK_BODY_PREFIX + ghParentIssue.getNumber();
    }

    /**
     * 
     * @param body
     * @return A string with the getSubTaskBodyPrefix in the beginning, and the rest of the body there.
     *         If we had the subTaskBodyPrefix in any other place of the body, it moves it to the beginning
     */
    public String applySubTaskBodyPrefix(String body) {
        String subTaskBodyPrefixString = getSubTaskBodyPrefix();
        String newBody = body;

        //let's try to find it
        if (StringUtils.contains(body, subTaskBodyPrefixString)) {
            //let's remove it.
            newBody = StringUtils.remove(body, subTaskBodyPrefixString);
        }

        newBody = subTaskBodyPrefixString + " " + newBody;

        return newBody;
    }

    /**
     * 
     * @param body
     * @return A string with the getSubTaskBodyPrefix in the beginning, and the rest of the body there.
     *         If we had the subTaskBodyPrefix in any other place of the body, it moves it to the beginning
     */
    public static String applySubTaskBodyPrefix(String body, GHIssue parentGHIssue) {
        String subTaskBodyPrefixString = getSubTaskBodyPrefix(parentGHIssue);
        String newBody = body == null ? "" : body;

        //let's try to find it
        if (StringUtils.contains(body, subTaskBodyPrefixString)) {
            //let's remove it.
            newBody = StringUtils.remove(body, subTaskBodyPrefixString);
        }

        newBody = subTaskBodyPrefixString + " " + newBody;

        return newBody;
    }

    public Issue getNewPrefilledIssue(Issue newIssueToReuse) {
        if (newIssueToReuse != null) {
            return newIssueToReuse;
        }
        Issue newIssue = new Issue();
        try {
            copyPropertiesTo(newIssue);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
                | TaskNotVisibleException e) {
            throw new Error("Error trying to prefill an Issue.", e);
        }
        return newIssue;
    }

    /**
     * 
     * @param newACTaskToReuse
     * @return the newACTaskToReuse if it isn't null, or a newly prefilled one from the dsiIssue.getAcTask()
     */
    private ACTask getNewPrefilledACTask(ACTask newACTaskToReuse) {
        if (newACTaskToReuse != null)
            return newACTaskToReuse;
        checkNotNull(getDSIObject());
        DSIIssue dsiIssue = (DSIIssue) getDSIObject();
        checkNotNull(dsiIssue);
        pt.ist.maidSyncher.domain.activeCollab.ACTask domainAcTask = dsiIssue.getAcTask();
        checkNotNull(domainAcTask);
        ACTask newAcTask = new ACTask();
        try {
            domainAcTask.copyPropertiesTo(newAcTask);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
                | TaskNotVisibleException e) {
            throw new Error("Error trying to prefill an Issue.", e);
        }
        return newAcTask;

    }

    private ACSubTask getNewPrefilledACSubTask(ACSubTask newACSubTaskToReuse) {
        if (newACSubTaskToReuse != null)
            return newACSubTaskToReuse;
        checkNotNull(getDSIObject());
        DSISubTask dsiSubTask = (DSISubTask) getDSIObject();
        checkNotNull(dsiSubTask);
        pt.ist.maidSyncher.domain.activeCollab.ACSubTask acSubTask = dsiSubTask.getAcSubTask();
        checkNotNull(acSubTask);
        ACSubTask newACSubTask = new ACSubTask();
        try {
            acSubTask.copyPropertiesTo(newACSubTask);
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException
                | TaskNotVisibleException e) {
            throw new Error("Error trying to prefill an Issue.", e);
        }
        return newACSubTask;
    }

    //Not used, TODO remove or use it
    //    private void updateSubTaskLabels(Set<SynchableObject> synchableObjectsToReturn) {
    //        //this will take care of incorrect uses of the labels
    //        validateAndCorrectLabels();
    //        //now let's assert if it changed the project label or not
    //        Optional<GHLabel> optionalGHLabel = Iterables.tryFind(getLabels(), new Predicate<GHLabel>() {
    //            @Override
    //            public boolean apply(GHLabel input) {
    //                if (input == null)
    //                    return false;
    //                DSIProject dsiProject = (DSIProject) input.getDSIObject();
    //                if (dsiProject != null && dsiProject.getAcProject() != null)
    //                    return true;
    //                return false;
    //            }
    //        });
    //        //let us check if we changed projects or not
    //
    //        ACProject projectLabelProject = ((DSIProject) projectLabel.getDSIObject()).getAcProject();
    //
    //    }

    //    private boolean projectChanged(Optional<GHLabel> optionalGHLabelWithAssociatedProject, ACProject currentProject) {
    //        if (optionalGHLabelWithAssociatedProject.isPresent() == false) {
    //            return ObjectUtils.equals(null, currentProject) == false;
    //        } else {
    //            ACProject acProject = ((DSIProject) optionalGHLabelWithAssociatedProject.get().getDSIObject()).getAcProject();
    //        }
    //
    //    }

    private SyncActionWrapper syncUpdateTaskEvent(ACTask newAcTask, DSIIssue dsiIssue, final SyncEvent syncEvent) {
        final Set<String> tickedDescriptors = new HashSet<>();
        boolean auxChangedMilestones = false;
        boolean auxChangedLabels = false;
        boolean auxChangedState = false;
        boolean auxChangedBody = false;
        boolean auxChangedTitle = false;
        for (String changedDescriptor : syncEvent.getChangedPropertyDescriptorNames().getUnmodifiableList()) {
            tickedDescriptors.add(changedDescriptor);
            switch (changedDescriptor) {
            case DSC_ID:
            case DSC_URL:
            case DSC_HTML_URL:
            case DSC_CREATED_AT:
            case DSC_UPDATED_AT:
            case DSC_NUMBER:
            case DSC_CLOSED_AT:
                break; //the ones above, there'se no sense in changing anything

            case DSC_MILESTONE:
                //we need to create/change that milestone on the AC side
                auxChangedMilestones = true;

                break;
            case DSC_LABELS:
                //seen that we were a subtask, if we edited the project label,
                //we need to create an ACTask on another ACProject
                auxChangedLabels = true;
                break;
            case DSC_STATE:
                //we might have to close/open the Task/SubTask on the other side
                auxChangedState = true;
                break;
            case DSC_BODY:
                //let us change the body on the other side as well
                auxChangedBody = true;
                break;
            case DSC_TITLE:
                //change the name on the other side
                auxChangedTitle = true;
                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 changedMilestones = auxChangedMilestones;
        final boolean changedLabels = auxChangedLabels;
        final boolean changedState = auxChangedState;
        final boolean changedBody = auxChangedBody;
        final boolean changedTitle = auxChangedTitle;
        return new SyncActionWrapper<SynchableObject>() {

            @Override
            public Set<SynchableObject> sync() throws SyncActionError {
                Set<SynchableObject> changedObjects = new HashSet<>();
                ACTask acTaskToEdit = null;
                DSIIssue dsiIssue = (DSIIssue) getDSIObject();
                try {
                    if (changedLabels) {
                        //let's assert the project label - if none is found, or more than one is found, let's assume the default project is to be used
                        ACProject acProject = getProjectToUsedBasedOnCurrentLabels();
                        ACProject currentACProject = dsiIssue.getAcTask().getProject();
                        if (ObjectUtils.notEqual(currentACProject, acProject)) {
                            //let's use the /move-to-project on the AC side
                            ACTask.moveTo(dsiIssue.getAcTask().getId(), currentACProject.getId(),
                                    acProject.getId());
                            //assuming all went well, we need to update this ACTask's projectId
                            dsiIssue.getAcTask().setProject(acProject);
                            changedObjects.add(dsiIssue.getAcTask());
                            DSIProject dsiProject = acProject.getDsiObjectProject();
                            dsiIssue.setProject(dsiProject);

                        }

                    }
                    if (changedMilestones) {
                        if (getMilestone() != null) {

                            //let us change the milestone on the other side.
                            //if the corresponding ACMilestone doesn't exist, reuse/create it

                            //let's try to find one with the name to use
                            ACProject acProject = getProjectToUsedBasedOnCurrentLabels();
                            pt.ist.maidSyncher.domain.activeCollab.ACMilestone milestoneToUse = pt.ist.maidSyncher.domain.activeCollab.ACMilestone
                                    .findMilestone(acProject, getMilestone().getTitle());
                            if (milestoneToUse == null) {
                                //we have to create it
                                ACMilestone acMilestoneToCreate = new ACMilestone();
                                acMilestoneToCreate.setName(getMilestone().getTitle());
                                acMilestoneToCreate.setBody(getMilestone().getDescription());
                                acMilestoneToCreate.setDueOn(getMilestone().getDueOn().toDate());
                                acMilestoneToCreate.setProjectId(acProject.getId());
                                ACMilestone newlyCreatedMilestone = ACMilestone.create(acMilestoneToCreate);
                                milestoneToUse = pt.ist.maidSyncher.domain.activeCollab.ACMilestone
                                        .process(newlyCreatedMilestone, true);
                                changedObjects.add(milestoneToUse);
                            }

                            acTaskToEdit = getNewPrefilledACTask(acTaskToEdit);
                            acTaskToEdit.setMilestoneId((int) milestoneToUse.getId());

                        }

                    }
                    if (changedState) {
                        //update the state on the other side
                        acTaskToEdit = getNewPrefilledACTask(acTaskToEdit);
                        acTaskToEdit.setComplete(getState().equalsIgnoreCase(STATE_CLOSED));

                    }
                    if (changedBody) {
                        //update the body on the other side
                        acTaskToEdit = getNewPrefilledACTask(acTaskToEdit);
                        acTaskToEdit.setBody(getBodyHtml());

                    }
                    if (changedTitle) {
                        //update the title on the other side
                        acTaskToEdit = getNewPrefilledACTask(acTaskToEdit);
                        acTaskToEdit.setName(getTitle());

                    }

                    if (acTaskToEdit != null) {
                        //let's edit it

                        //get the base url
                        pt.ist.maidSyncher.domain.activeCollab.ACTask acTask = dsiIssue.getAcTask();
                        ACTask updatedAcTask = acTaskToEdit.update(acTask.getUrl());
                        changedObjects.add(pt.ist.maidSyncher.domain.activeCollab.ACTask.process(updatedAcTask,
                                acTask.getProject().getId(), true));

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

                return changedObjects;
            }

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

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

            @Override
            public Set<Class> getSyncDependedTypesOfDSIObjects() {
                Set<Class> classesDependedOn = new HashSet();
                classesDependedOn.add(ACProject.class);
                classesDependedOn.add(ACMilestone.class);
                classesDependedOn.add(GHMilestone.class);
                classesDependedOn.add(GHLabel.class);
                return Collections.singleton((Class) ACProject.class);
            }

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

    private ACProject getProjectToUsedBasedOnCurrentLabels() {

        Collection<GHLabel> appliableLabels = Collections2.filter(getLabelsSet(), new Predicate<GHLabel>() {
            @Override
            public boolean apply(GHLabel input) {
                if (input == null)
                    return false;
                DSIProject dsiProject = (DSIProject) input.getDSIObject();
                if (dsiProject == null)
                    return false;
                if (dsiProject.getAcProject() != null)
                    return true;
                return false;
            }
        });
        //let's try to search on the current labels
        if (appliableLabels.size() != 1) {
            return ((DSIRepository) getRepository().getDSIObject()).getDefaultProject();
        } else
            return ((DSIProject) appliableLabels.iterator().next().getDSIObject()).getAcProject();
    }

    private SyncActionWrapper syncCreateEvent(final ACTask newAcTask, final SyncEvent syncEvent) {
        final Set<String> tickedDescriptors = new HashSet<>();
        for (String changedDescriptor : syncEvent.getChangedPropertyDescriptorNames().getUnmodifiableList()) {
            tickedDescriptors.add(changedDescriptor);
            switch (changedDescriptor) {
            case DSC_ID:
            case DSC_URL:
            case DSC_HTML_URL:
            case DSC_CREATED_AT:
            case DSC_UPDATED_AT:
            case DSC_NUMBER:

            case DSC_MILESTONE:
            case DSC_LABELS:
            case DSC_STATE:
            case DSC_BODY:
            case DSC_TITLE:
            case DSC_CLOSED_AT:
                break;
            default:
                tickedDescriptors.remove(changedDescriptor); //if we did not fall on any of the above
                //cases, let's remove it from the ticked descriptors

            }
        }

        //if we have a GHLabel of 'deleted' let's not do anything
        if (GHLabel.containsDeletedLabel(getLabelsSet())) {
            return new EmptySyncActionWrapper(syncEvent);
        } else {

            SyncActionWrapper toReturnActionWrapper = new SyncActionWrapper() {

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

                    //let's fill appropriately the newTask
                    newAcTask.setName(getTitle());
                    newAcTask.setBody(getBodyHtml());
                    newAcTask.setVisibility(true);

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

                        if (getState().equals(STATE_CLOSED)) {
                            newAcTask.setComplete(true);
                        } else if (getState().equals(STATE_OPEN)) {
                            newAcTask.setComplete(false);
                        }

                        DSIRepository dsiRepository = (DSIRepository) getRepository().getDSIObject(); //depended on
                        //we will also need the project on the AC side
                        DSIIssue dsiIssue = (DSIIssue) getDSIObject(); /*seen that we are creating based on something
                                                                       from GH, we are creating an issue, thus the cast */
                        //let's find out which project we should be creating the task on
                        //in this version, let's use the default one always.
                        //TODO use the labels to create the task in various projects GHIssue #10

                        final ACProject acProject = dsiRepository.getDefaultProject();
                        //now, the harder stuff, Milestones, and Category [aka repository]

                        //let's see if the Milestone exists
                        if (getMilestone() != null) {

                            DSIMilestone dsiMilestone = (DSIMilestone) getMilestone().getDSIObject(); //depended on
                            if (dsiMilestone == null || dsiMilestone.getAcMilestone(acProject) == null) {
                                ACMilestone acMilestoneToCreate = getMilestone()
                                        .getACCorrespondingPreliminarObject(acProject.getId());
                                pt.ist.maidSyncher.domain.activeCollab.ACMilestone newMilestone = pt.ist.maidSyncher.domain.activeCollab.ACMilestone
                                        .process(ACMilestone.create(acMilestoneToCreate), true);
                                changedObjects.add(newMilestone);
                                if (dsiMilestone == null) {
                                    dsiMilestone = (DSIMilestone) getMilestone().findOrCreateDSIObject();
                                }
                                dsiMilestone.addAcMilestones(newMilestone);

                            } else {
                                pt.ist.maidSyncher.domain.activeCollab.ACMilestone acMilestone = dsiMilestone
                                        .getAcMilestone(acProject);
                                //TODO BUG? shouldn't we check if acMilestone == null?!
                                if (ObjectUtils.equals(acMilestone.getProject(), acProject) == false) {
                                    //then this milestone should be moved,or copied to the new project
                                    //let's see which one we should do
                                    boolean hasOtherTasks = acMilestone.getTasksSet().isEmpty() == false;

                                    if (hasOtherTasks) {
                                        //let's copy it
                                        pt.ist.maidSyncher.domain.activeCollab.ACMilestone newMilestone = pt.ist.maidSyncher.domain.activeCollab.ACMilestone
                                                .process(ACMilestone.copyTo(acMilestone.getId(),
                                                        acMilestone.getProject().getId(), acProject.getId()), true);
                                        dsiMilestone.addAcMilestones(newMilestone);
                                        changedObjects.add(newMilestone);
                                    } else {
                                        //let's move it
                                        ACMilestone.moveTo(acMilestone.getId(), acMilestone.getProject().getId(),
                                                acProject.getId());
                                        //let's make the move internally without fuss
                                        acMilestone.setProject(acProject);
                                        changedObjects.add(acMilestone);
                                    }

                                }
                            }
                            pt.ist.maidSyncher.domain.activeCollab.ACMilestone acMilestone = dsiMilestone
                                    .getAcMilestone(acProject);
                            long id = acMilestone.getId();
                            newAcTask.setMilestoneId((int) id);
                        }

                        ACTaskCategory acTaskCategory = dsiRepository.getACTaskCategoryFor(acProject);

                        newAcTask.setCategoryId((int) acTaskCategory.getId());

                        ACTask newlyCreatedTask = ACTask.createTask(newAcTask, acProject.getId());
                        pt.ist.maidSyncher.domain.activeCollab.ACTask newlyCreatedDomainAcTask = pt.ist.maidSyncher.domain.activeCollab.ACTask
                                .process(newlyCreatedTask, acProject.getId(), true);
                        changedObjects.add(newlyCreatedDomainAcTask);

                        dsiIssue.setAcTask(newlyCreatedDomainAcTask);
                    } catch (IOException ex) {
                        throw new SyncActionError(ex, changedObjects);
                    }
                    return changedObjects;
                }

                @Override
                public Collection<DSIObject> getSyncDependedDSIObjects() {
                    Set<DSIObject> dsiObjectsDependedOn = new HashSet<>();
                    //                dsiObjectsDependedOn.add(getMilestone().getDSIObject());
                    dsiObjectsDependedOn.add(getRepository().getDSIObject());
                    dsiObjectsDependedOn.add(((DSIRepository) getRepository().getDSIObject()).getDefaultProject()
                            .getDsiObjectProject());
                    dsiObjectsDependedOn.add(null); //null - means all of the GHLabel's should be synched
                    return dsiObjectsDependedOn;
                }

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

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

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

    }

}