org.zkoss.ganttz.data.GanttDiagramGraph.java Source code

Java tutorial

Introduction

Here is the source code for org.zkoss.ganttz.data.GanttDiagramGraph.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolxico de Galicia
 * Copyright (C) 2010-2011 Igalia, S.L.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.zkoss.ganttz.data;

import static java.util.Arrays.asList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jgrapht.DirectedGraph;
import org.jgrapht.graph.SimpleDirectedGraph;
import org.zkoss.ganttz.data.DependencyType.Point;
import org.zkoss.ganttz.data.ITaskFundamentalProperties.IModifications;
import org.zkoss.ganttz.data.ITaskFundamentalProperties.IUpdatablePosition;
import org.zkoss.ganttz.data.constraint.Constraint;
import org.zkoss.ganttz.data.constraint.ConstraintOnComparableValues;
import org.zkoss.ganttz.data.constraint.ConstraintOnComparableValues.ComparisonType;
import org.zkoss.ganttz.data.criticalpath.ICriticalPathCalculable;
import org.zkoss.ganttz.util.IAction;
import org.zkoss.ganttz.util.PreAndPostNotReentrantActionsWrapper;
import org.zkoss.ganttz.util.ReentranceGuard;
import org.zkoss.ganttz.util.ReentranceGuard.IReentranceCases;

/**
 * This class contains a graph with the {@link Task tasks} as vertexes and the
 * {@link Dependency dependency} as arcs. It enforces the rules embodied in the
 * dependencies and in the duration of the tasks using listeners. <br/>
 *
 * @author scar Gonzlez Fernndez <ogonzalez@igalia.com>
 */
public class GanttDiagramGraph<V, D extends IDependency<V>> implements ICriticalPathCalculable<V> {

    private static final Log LOG = LogFactory.getLog(GanttDiagramGraph.class);

    public static IDependenciesEnforcerHook doNothingHook() {
        return new IDependenciesEnforcerHook() {

            @Override
            public void setNewEnd(GanttDate previousEnd, GanttDate newEnd) {
            }

            @Override
            public void setStartDate(GanttDate previousStart, GanttDate previousEnd, GanttDate newStart) {
            }

            @Override
            public void positionPotentiallyModified() {
            }
        };
    }

    private static final GanttZKAdapter GANTTZK_ADAPTER = new GanttZKAdapter();

    public static IAdapter<Task, Dependency> taskAdapter() {
        return GANTTZK_ADAPTER;
    }

    public interface IAdapter<V, D extends IDependency<V>> {
        List<V> getChildren(V task);

        V getOwner(V task);

        boolean isContainer(V task);

        void registerDependenciesEnforcerHookOn(V task, IDependenciesEnforcerHookFactory<V> hookFactory);

        GanttDate getStartDate(V task);

        void setStartDateFor(V task, GanttDate newStart);

        GanttDate getEndDateFor(V task);

        void setEndDateFor(V task, GanttDate newEnd);

        List<Constraint<GanttDate>> getConstraints(ConstraintCalculator<V> calculator, Set<D> withDependencies,
                Point point);

        List<Constraint<GanttDate>> getStartConstraintsFor(V task);

        List<Constraint<GanttDate>> getEndConstraintsFor(V task);

        V getSource(D dependency);

        V getDestination(D dependency);

        Class<D> getDependencyType();

        D createInvisibleDependency(V origin, V destination, DependencyType type);

        DependencyType getType(D dependency);

        boolean isVisible(D dependency);

        boolean isFixed(V task);

    }

    public static class GanttZKAdapter implements IAdapter<Task, Dependency> {

        @Override
        public List<Task> getChildren(Task task) {
            return task.getTasks();
        }

        @Override
        public Task getOwner(Task task) {
            if (task instanceof Milestone) {
                Milestone milestone = (Milestone) task;
                return milestone.getOwner();
            }

            return null;
        }

        @Override
        public Task getDestination(Dependency dependency) {
            return dependency.getDestination();
        }

        @Override
        public Task getSource(Dependency dependency) {
            return dependency.getSource();
        }

        @Override
        public boolean isContainer(Task task) {
            return task.isContainer();
        }

        @Override
        public void registerDependenciesEnforcerHookOn(Task task,
                IDependenciesEnforcerHookFactory<Task> hookFactory) {
            task.registerDependenciesEnforcerHook(hookFactory);
        }

        @Override
        public Dependency createInvisibleDependency(Task origin, Task destination, DependencyType type) {
            return new Dependency(origin, destination, type, false);
        }

        @Override
        public Class<Dependency> getDependencyType() {
            return Dependency.class;
        }

        @Override
        public DependencyType getType(Dependency dependency) {
            return dependency.getType();
        }

        @Override
        public boolean isVisible(Dependency dependency) {
            return dependency.isVisible();
        }

        @Override
        public GanttDate getEndDateFor(Task task) {
            return task.getEndDate();
        }

        @Override
        public void setEndDateFor(Task task, final GanttDate newEnd) {
            task.doPositionModifications(new IModifications() {
                @Override
                public void doIt(IUpdatablePosition position) {
                    position.setEndDate(newEnd);
                }
            });
        }

        @Override
        public GanttDate getStartDate(Task task) {
            return task.getBeginDate();
        }

        @Override
        public void setStartDateFor(Task task, final GanttDate newStart) {
            task.doPositionModifications(new IModifications() {
                @Override
                public void doIt(IUpdatablePosition position) {
                    position.setBeginDate(newStart);
                }
            });
        }

        @Override
        public List<Constraint<GanttDate>> getConstraints(ConstraintCalculator<Task> calculator,
                Set<Dependency> withDependencies, Point pointBeingModified) {

            return Dependency.getConstraintsFor(calculator, withDependencies, pointBeingModified);
        }

        @Override
        public List<Constraint<GanttDate>> getStartConstraintsFor(Task task) {
            return task.getStartConstraints();
        }

        @Override
        public List<Constraint<GanttDate>> getEndConstraintsFor(Task task) {
            return task.getEndConstraints();
        }

        @Override
        public boolean isFixed(Task task) {
            return task.isFixed();
        }

    }

    public static class GanttZKDiagramGraph extends GanttDiagramGraph<Task, Dependency> {

        private GanttZKDiagramGraph(boolean scheduleBackwards, List<Constraint<GanttDate>> globalStartConstraints,
                List<Constraint<GanttDate>> globalEndConstraints, boolean dependenciesConstraintsHavePriority) {

            super(scheduleBackwards, GANTTZK_ADAPTER, globalStartConstraints, globalEndConstraints,
                    dependenciesConstraintsHavePriority);
        }

    }

    public interface IGraphChangeListener {
        void execute();
    }

    public static GanttZKDiagramGraph create(boolean scheduleBackwards,
            List<Constraint<GanttDate>> globalStartConstraints, List<Constraint<GanttDate>> globalEndConstraints,
            boolean dependenciesConstraintsHavePriority) {

        return new GanttZKDiagramGraph(scheduleBackwards, globalStartConstraints, globalEndConstraints,
                dependenciesConstraintsHavePriority);
    }

    private final IAdapter<V, D> adapter;

    private final DirectedGraph<V, D> graph;

    private final TopologicalSorter topologicalSorter;

    private List<V> topLevelTasks = new ArrayList<>();

    private Map<V, V> fromChildToParent = new HashMap<>();

    private final List<Constraint<GanttDate>> globalStartConstraints;

    private final List<Constraint<GanttDate>> globalEndConstraints;

    private final boolean scheduleBackwards;

    private DependenciesEnforcer enforcer = new DependenciesEnforcer();

    private final boolean dependenciesConstraintsHavePriority;

    private final ReentranceGuard positionsUpdatingGuard = new ReentranceGuard();

    private final PreAndPostNotReentrantActionsWrapper preAndPostActions = new PreAndPostNotReentrantActionsWrapper() {

        @Override
        protected void postAction() {
            executeGraphChangeListeners(new ArrayList<>(postGraphChangeListeners));
        }

        @Override
        protected void preAction() {
            executeGraphChangeListeners(new ArrayList<>(preGraphChangeListeners));
        }

        private void executeGraphChangeListeners(List<IGraphChangeListener> graphChangeListeners) {
            for (IGraphChangeListener each : graphChangeListeners) {
                try {
                    each.execute();
                } catch (Exception e) {
                    LOG.error("error executing execution listener", e);
                }
            }
        }
    };

    private List<IGraphChangeListener> preGraphChangeListeners = new ArrayList<>();

    private List<IGraphChangeListener> postGraphChangeListeners = new ArrayList<>();

    public void addPreGraphChangeListener(IGraphChangeListener preGraphChangeListener) {
        preGraphChangeListeners.add(preGraphChangeListener);
    }

    public void removePreGraphChangeListener(IGraphChangeListener preGraphChangeListener) {
        preGraphChangeListeners.remove(preGraphChangeListener);
    }

    public void addPostGraphChangeListener(IGraphChangeListener postGraphChangeListener) {
        postGraphChangeListeners.add(postGraphChangeListener);
    }

    public void removePostGraphChangeListener(IGraphChangeListener postGraphChangeListener) {
        postGraphChangeListeners.remove(postGraphChangeListener);
    }

    public void addPreChangeListeners(Collection<? extends IGraphChangeListener> preChangeListeners) {
        for (IGraphChangeListener each : preChangeListeners) {
            addPreGraphChangeListener(each);
        }
    }

    public void addPostChangeListeners(Collection<? extends IGraphChangeListener> postChangeListeners) {
        for (IGraphChangeListener each : postChangeListeners) {
            addPostGraphChangeListener(each);
        }
    }

    public static <V, D extends IDependency<V>> GanttDiagramGraph<V, D> create(boolean scheduleBackwards,
            IAdapter<V, D> adapter, List<Constraint<GanttDate>> globalStartConstraints,
            List<Constraint<GanttDate>> globalEndConstraints, boolean dependenciesConstraintsHavePriority) {

        return new GanttDiagramGraph<>(scheduleBackwards, adapter, globalStartConstraints, globalEndConstraints,
                dependenciesConstraintsHavePriority);
    }

    protected GanttDiagramGraph(boolean scheduleBackwards, IAdapter<V, D> adapter,
            List<Constraint<GanttDate>> globalStartConstraints, List<Constraint<GanttDate>> globalEndConstraints,
            boolean dependenciesConstraintsHavePriority) {

        this.scheduleBackwards = scheduleBackwards;
        this.adapter = adapter;
        this.globalStartConstraints = globalStartConstraints;
        this.globalEndConstraints = globalEndConstraints;
        this.dependenciesConstraintsHavePriority = dependenciesConstraintsHavePriority;
        this.graph = new SimpleDirectedGraph<>(adapter.getDependencyType());
        this.topologicalSorter = new TopologicalSorter();
    }

    public void enforceAllRestrictions() {
        enforcer.enforceRestrictionsOn(withoutVisibleIncomingDependencies(getTopLevelTasks()));
    }

    private List<V> withoutVisibleIncomingDependencies(Collection<? extends V> tasks) {
        List<V> result = new ArrayList<>();
        for (V each : tasks) {

            boolean condition = noVisibleDependencies(
                    isScheduleForward() ? graph.incomingEdgesOf(each) : graph.outgoingEdgesOf(each));

            if (condition) {
                result.add(each);
            }
        }

        return result;
    }

    private boolean noVisibleDependencies(Collection<? extends D> dependencies) {
        for (D each : dependencies) {
            if (adapter.isVisible(each)) {
                return false;
            }
        }

        return true;
    }

    public void addTopLevel(V task) {
        topLevelTasks.add(task);
        addTask(task);
    }

    public void addTopLevel(Collection<? extends V> tasks) {
        for (V task : tasks) {
            addTopLevel(task);
        }
    }

    public void addTasks(Collection<? extends V> tasks) {
        for (V t : tasks) {
            addTask(t);
        }
    }

    /**
     * This class is designed for performing topological sorting of nodes.
     * Topological sorting is used to make graph nodes not to be mixed, because
     * parent and child nodes ({@link TaskPoint}) must be placed in the correct order.
     * Also during topological sorting nodes are placed on appropriate levels.
     * Topological sorting can be done using different algorithms, but here is used Khan's algorithm.
     */
    class TopologicalSorter {

        private Map<TaskPoint, Integer> taskPointsByDepthCached = null;

        /**
         * This method is used to place each node on appropriate level.
         *
         * @return map of TaskPoints with appropriate levels
         */
        private Map<TaskPoint, Integer> taskPointsByDepth() {
            if (taskPointsByDepthCached != null) {
                return taskPointsByDepthCached;
            }

            Map<TaskPoint, Integer> result = new HashMap<>();
            Map<TaskPoint, Set<TaskPoint>> visitedBy = new HashMap<>();

            /*
             * Here are stored taskpoints that we have visited.
             * This need to be done because we need to check have we visited this taskpoint, or not.
             * Described above need to be done to avoid loop between taskpoints.
             */
            Set<TaskPoint> visitedTaskPoints = new HashSet();

            Queue<TaskPoint> withoutIncoming = getInitial(withoutVisibleIncomingDependencies(getTopLevelTasks()));
            for (TaskPoint each : withoutIncoming) {
                initializeIfNeededForKey(result, each, 0);
            }

            while (!withoutIncoming.isEmpty()) {

                TaskPoint current = withoutIncoming.poll();

                visitedTaskPoints.add(current); // Marking taskpoint as visited

                // Taking all child elements
                for (TaskPoint each : current.getImmediateSuccessors()) {

                    if (!visitedTaskPoints.contains(each)) {

                        initializeIfNeededForKey(visitedBy, each, new HashSet<TaskPoint>());

                        Set<TaskPoint> visitors = visitedBy.get(each);
                        visitors.add(current);

                        // Taking parent elements
                        Set<TaskPoint> predecessorsRequired = each.getImmediatePredecessors();

                        if (visitors.containsAll(predecessorsRequired)) {

                            initializeIfNeededForKey(result, each, result.get(current) + 1);

                            withoutIncoming.offer(each);
                        }
                    }
                }
            }

            return taskPointsByDepthCached = Collections.unmodifiableMap(result);
        }

        private <K, T> void initializeIfNeededForKey(Map<K, T> map, K key, T initialValue) {
            if (!map.containsKey(key)) {
                map.put(key, initialValue);
            }
        }

        private LinkedList<TaskPoint> getInitial(List<V> initial) {
            LinkedList<TaskPoint> result = new LinkedList<>();
            for (V each : initial) {
                result.add(allPointsPotentiallyModified(each));
            }

            return result;
        }

        public void recalculationNeeded() {
            taskPointsByDepthCached = null;
        }

        public List<Recalculation> sort(Collection<? extends Recalculation> recalculationsToBeSorted) {

            List<Recalculation> result = new ArrayList<>(recalculationsToBeSorted);
            final Map<TaskPoint, Integer> taskPointsByDepth = taskPointsByDepth();
            Collections.sort(result, new Comparator<Recalculation>() {

                @Override
                public int compare(Recalculation o1, Recalculation o2) {
                    int o1Depth = onNullDefault(taskPointsByDepth.get(o1.taskPoint), Integer.MAX_VALUE,
                            "no depth value for " + o1.taskPoint);

                    int o2Depth = onNullDefault(taskPointsByDepth.get(o2.taskPoint), Integer.MAX_VALUE,
                            "no depth value for " + o2.taskPoint);

                    int result = o1Depth - o2Depth;

                    if (result == 0) {
                        return asInt(o1.parentRecalculation) - asInt(o2.parentRecalculation);
                    }

                    return result;
                }

                private int asInt(boolean b) {
                    return b ? 1 : 0;
                }
            });

            return result;
        }
    }

    private static <T> T onNullDefault(T value, T defaultValue, String warnMessage) {
        if (value == null) {
            if (warnMessage != null) {
                LOG.warn(warnMessage);
            }

            return defaultValue;
        }

        return value;
    }

    public void addTask(V original) {
        List<V> stack = new LinkedList<>();
        stack.add(original);
        List<D> dependenciesToAdd = new ArrayList<>();

        while (!stack.isEmpty()) {

            V task = stack.remove(0);
            graph.addVertex(task);
            topologicalSorter.recalculationNeeded();
            adapter.registerDependenciesEnforcerHookOn(task, enforcer);

            if (adapter.isContainer(task)) {

                for (V child : adapter.getChildren(task)) {
                    fromChildToParent.put(child, task);
                    stack.add(0, child);
                    dependenciesToAdd.add(adapter.createInvisibleDependency(child, task, DependencyType.END_END));
                    dependenciesToAdd
                            .add(adapter.createInvisibleDependency(task, child, DependencyType.START_START));
                }

            } else {
                V owner = adapter.getOwner(task);
                if (owner != null) {
                    dependenciesToAdd.add(adapter.createInvisibleDependency(task, owner, DependencyType.END_END));
                    dependenciesToAdd
                            .add(adapter.createInvisibleDependency(owner, task, DependencyType.START_START));
                }
            }
        }

        for (D each : dependenciesToAdd) {
            add(each, false);
        }
    }

    private interface IDependenciesEnforcer {
        void setStartDate(GanttDate previousStart, GanttDate previousEnd, GanttDate newStart);

        void setNewEnd(GanttDate previousEnd, GanttDate newEnd);
    }

    /**
     * <p>
     * When a {@link Task}'s dates are modified methods of this interface must
     * be called. This can potentially trigger the dependencies enforcement
     * algorithm.
     * </p>
     * <p>
     * If the date modification happens outside the dependencies enforcement
     * algorithm, it's always executed. Through the algorithm execution other
     * tasks' dates are modified. When this happens we don't want to trigger the
     * algorithm, instead we want to record that the change has happened and
     * when the algorithm ends all the tasks are notified at once.
     * </p>
     * <p>
     * For example imagine a Gantt with three tasks: T1 -> T2 -> T3. Imagine
     * that T1 position is modified due to being moved by the user. In that case
     * the scheduling algorithm triggers and the {@link Recalculation}
     * recalculations needed are done. T2 position would be recalculated and T3
     * position too. When the recalculation happens their dates are modified,
     * but in that case we don't want to trigger the dependencies enforcement
     * algorithm again. What we want is to record the changes that have happened
     * due to the algorithm. When the algorithm ends all notifications are fired
     * at once. These notifications are notified to the registered
     * {@link INotificationAfterDependenciesEnforcement}.
     * </p>
     */
    public interface IDependenciesEnforcerHook extends IDependenciesEnforcer {
        void positionPotentiallyModified();
    }

    public interface IDependenciesEnforcerHookFactory<T> {
        /**
         * Creates a {@link IDependenciesEnforcerHook} that uses the provided
         * {@link INotificationAfterDependenciesEnforcement notifier} to notify
         * the changes that have happened due to the algorithm.
         */
        IDependenciesEnforcerHook create(T task, INotificationAfterDependenciesEnforcement notifier);

        IDependenciesEnforcerHook create(T task);
    }

    public interface INotificationAfterDependenciesEnforcement {
        void onStartDateChange(GanttDate previousStart, GanttDate previousEnd, GanttDate newStart);

        void onEndDateChange(GanttDate previousEnd, GanttDate newEnd);
    }

    private static final INotificationAfterDependenciesEnforcement EMPTY_NOTIFICATOR = new INotificationAfterDependenciesEnforcement() {

        @Override
        public void onStartDateChange(GanttDate previousStart, GanttDate previousEnd, GanttDate newStart) {
        }

        @Override
        public void onEndDateChange(GanttDate previousEnd, GanttDate newEnd) {
        }
    };

    /**
     * Tracks all modifications to dates that have happened inside the
     * dependencies enforcement algorithm. At the end of the algorithm they're
     * executed via {@link DeferedNotifier#doNotifications()}.
     */
    public class DeferedNotifier {

        private Map<V, NotificationPendingForTask> notificationsPending = new LinkedHashMap<>();

        public void add(V task, StartDateNofitication notification) {
            retrieveOrCreateFor(task).setStartDateNofitication(notification);
        }

        private NotificationPendingForTask retrieveOrCreateFor(V task) {
            NotificationPendingForTask result = notificationsPending.get(task);
            if (result == null) {
                result = new NotificationPendingForTask();
                notificationsPending.put(task, result);
            }

            return result;
        }

        void add(V task, LengthNotification notification) {
            retrieveOrCreateFor(task).setLengthNofitication(notification);
        }

        public void doNotifications() {
            for (NotificationPendingForTask each : notificationsPending.values()) {
                each.doNotification();
            }
            notificationsPending.clear();
        }

    }

    private class NotificationPendingForTask {
        private StartDateNofitication startDateNofitication;

        private LengthNotification lengthNofitication;

        void setStartDateNofitication(StartDateNofitication startDateNofitication) {

            this.startDateNofitication = (this.startDateNofitication == null) ? startDateNofitication
                    : this.startDateNofitication.coalesce(startDateNofitication);
        }

        void setLengthNofitication(LengthNotification lengthNofitication) {

            this.lengthNofitication = (this.lengthNofitication == null) ? lengthNofitication
                    : this.lengthNofitication.coalesce(lengthNofitication);
        }

        void doNotification() {
            if (startDateNofitication != null) {
                startDateNofitication.doNotification();
            }

            if (lengthNofitication != null) {
                lengthNofitication.doNotification();
            }
        }
    }

    private class StartDateNofitication {

        private final INotificationAfterDependenciesEnforcement notification;
        private final GanttDate previousStart;
        private final GanttDate previousEnd;
        private final GanttDate newStart;

        public StartDateNofitication(INotificationAfterDependenciesEnforcement notification,
                GanttDate previousStart, GanttDate previousEnd, GanttDate newStart) {

            this.notification = notification;
            this.previousStart = previousStart;
            this.previousEnd = previousEnd;
            this.newStart = newStart;
        }

        public StartDateNofitication coalesce(StartDateNofitication startDateNofitication) {
            return new StartDateNofitication(notification, previousStart, previousEnd,
                    startDateNofitication.newStart);
        }

        void doNotification() {
            notification.onStartDateChange(previousStart, previousEnd, newStart);
        }
    }

    private class LengthNotification {

        private final INotificationAfterDependenciesEnforcement notification;
        private final GanttDate previousEnd;
        private final GanttDate newEnd;

        public LengthNotification(INotificationAfterDependenciesEnforcement notification, GanttDate previousEnd,
                GanttDate newEnd) {

            this.notification = notification;
            this.previousEnd = previousEnd;
            this.newEnd = newEnd;

        }

        public LengthNotification coalesce(LengthNotification lengthNofitication) {
            return new LengthNotification(notification, previousEnd, lengthNofitication.newEnd);
        }

        void doNotification() {
            notification.onEndDateChange(previousEnd, newEnd);
        }
    }

    private class DependenciesEnforcer implements IDependenciesEnforcerHookFactory<V> {

        private ThreadLocal<DeferedNotifier> deferedNotifier = new ThreadLocal<>();

        /**
         * It creates a {@link IDependenciesEnforcerHook} that starts the
         * algorithm <em>onEntrance</em> and in subsequent tasks position
         * modifications records the changes <em>onNotification</em>.
         */
        @Override
        public IDependenciesEnforcerHook create(V task, INotificationAfterDependenciesEnforcement notificator) {
            return withPositionPotentiallyModified(task,
                    onlyEnforceDependenciesOnEntrance(onEntrance(task), onNotification(task, notificator)));
        }

        @Override
        public IDependenciesEnforcerHook create(V task) {
            return create(task, EMPTY_NOTIFICATOR);
        }

        /**
         * What to do when a task's position is modified not inside the
         * dependencies enforcement algorithm.
         */
        private IDependenciesEnforcer onEntrance(final V task) {
            return new IDependenciesEnforcer() {

                public void setStartDate(GanttDate previousStart, GanttDate previousEnd, GanttDate newStart) {
                    taskPositionModified(task);
                }

                @Override
                public void setNewEnd(GanttDate previousEnd, GanttDate newEnd) {
                    taskPositionModified(task);
                }

            };
        }

        /**
         * What to do when a task's position is modified from inside the
         * dependencies enforcement algorithm.
         */
        private IDependenciesEnforcer onNotification(final V task,
                final INotificationAfterDependenciesEnforcement notification) {
            return new IDependenciesEnforcer() {

                @Override
                public void setStartDate(GanttDate previousStart, GanttDate previousEnd, GanttDate newStart) {

                    StartDateNofitication startDateNotification = new StartDateNofitication(notification,
                            previousStart, previousEnd, newStart);

                    deferedNotifier.get().add(task, startDateNotification);

                }

                @Override
                public void setNewEnd(GanttDate previousEnd, GanttDate newEnd) {
                    LengthNotification lengthNotification = new LengthNotification(notification, previousEnd,
                            newEnd);
                    deferedNotifier.get().add(task, lengthNotification);
                }
            };

        }

        /**
         * Enrich {@link IDependenciesEnforcer} with
         * {@link IDependenciesEnforcerHook#positionPotentiallyModified()}.
         */
        private IDependenciesEnforcerHook withPositionPotentiallyModified(final V task,
                final IDependenciesEnforcer enforcer) {
            return new IDependenciesEnforcerHook() {

                @Override
                public void setStartDate(GanttDate previousStart, GanttDate previousEnd, GanttDate newStart) {
                    enforcer.setStartDate(previousStart, previousEnd, newStart);
                }

                @Override
                public void setNewEnd(GanttDate previousEnd, GanttDate newEnd) {
                    enforcer.setNewEnd(previousEnd, newEnd);
                }

                @Override
                public void positionPotentiallyModified() {
                    taskPositionModified(task);
                }
            };
        }

        /**
         * Creates a {@link IDependenciesEnforcer} that detects if a position
         * change comes from the dependencies algorithm or comes from outside.
         * For that a {@link ReentranceGuard} is used. If the dependencies
         * enforcement algorithm isn't being executed the
         * {@link IDependenciesEnforcer} created delegates to
         * <code>onEntrance</code>. Otherwise it delegates to
         * <code>notifier</code>.
         */
        private IDependenciesEnforcer onlyEnforceDependenciesOnEntrance(final IDependenciesEnforcer onEntrance,
                final IDependenciesEnforcer notifier) {
            return new IDependenciesEnforcer() {

                @Override
                public void setStartDate(final GanttDate previousStart, final GanttDate previousEnd,
                        final GanttDate newStart) {

                    positionsUpdatingGuard.entranceRequested(new IReentranceCases() {
                        @Override
                        public void ifNewEntrance() {
                            onNewEntrance(new IAction() {

                                @Override
                                public void doAction() {
                                    notifier.setStartDate(previousStart, previousEnd, newStart);
                                    onEntrance.setStartDate(previousStart, previousEnd, newStart);
                                }
                            });
                        }

                        @Override
                        public void ifAlreadyInside() {
                            notifier.setStartDate(previousStart, previousEnd, newStart);

                        }
                    });
                }

                @Override
                public void setNewEnd(final GanttDate previousEnd, final GanttDate newEnd) {
                    positionsUpdatingGuard.entranceRequested(new IReentranceCases() {

                        @Override
                        public void ifNewEntrance() {
                            onNewEntrance(new IAction() {

                                @Override
                                public void doAction() {
                                    notifier.setNewEnd(previousEnd, newEnd);
                                    onEntrance.setNewEnd(previousEnd, newEnd);
                                }
                            });
                        }

                        @Override
                        public void ifAlreadyInside() {
                            notifier.setNewEnd(previousEnd, newEnd);
                        }
                    });
                }
            };

        }

        void enforceRestrictionsOn(Collection<? extends V> tasks) {
            List<Recalculation> allRecalculations = new ArrayList<>();
            for (V each : tasks) {
                allRecalculations.addAll(getRecalculationsNeededFrom(each));
            }
            enforceRestrictionsOn(allRecalculations, tasks);
        }

        void enforceRestrictionsOn(V task) {
            enforceRestrictionsOn(getRecalculationsNeededFrom(task), Collections.singleton(task));
        }

        void enforceRestrictionsOn(final List<Recalculation> recalculations,
                final Collection<? extends V> initiallyModified) {

            executeWithPreAndPostActionsOnlyIfNewEntrance(new IAction() {
                @Override
                public void doAction() {
                    doRecalculations(recalculations, initiallyModified);
                }
            });
        }

        private void executeWithPreAndPostActionsOnlyIfNewEntrance(final IAction action) {
            positionsUpdatingGuard.entranceRequested(new IReentranceCases() {

                @Override
                public void ifAlreadyInside() {
                    action.doAction();
                }

                @Override
                public void ifNewEntrance() {
                    onNewEntrance(action);
                }
            });
        }

        /**
         * When entering and exiting the dependencies enforcement algorithm some
         * listeners must be notified.
         */
        private void onNewEntrance(final IAction action) {
            preAndPostActions.doAction(decorateWithNotifications(action));
        }

        /**
         * Attach the {@link DeferedNotifier} for the current execution.
         * {@link DeferedNotifier#doNotifications()} is called once the
         * execution has finished, telling all listeners the task positions
         * modifications that have happened.
         */
        private IAction decorateWithNotifications(final IAction action) {
            return new IAction() {

                @Override
                public void doAction() {
                    deferedNotifier.set(new DeferedNotifier());
                    try {
                        action.doAction();
                    } finally {
                        DeferedNotifier notifier = deferedNotifier.get();
                        notifier.doNotifications();
                        deferedNotifier.set(null);
                    }
                }
            };
        }

        DeferedNotifier manualNotification(final IAction action) {
            final DeferedNotifier result = new DeferedNotifier();
            positionsUpdatingGuard.entranceRequested(new IReentranceCases() {

                @Override
                public void ifAlreadyInside() {
                    throw new RuntimeException("it cannot do a manual notification if it's already inside");
                }

                @Override
                public void ifNewEntrance() {
                    preAndPostActions.doAction(new IAction() {

                        @Override
                        public void doAction() {
                            deferedNotifier.set(result);
                            try {
                                action.doAction();
                            } finally {
                                deferedNotifier.set(null);
                            }
                        }
                    });
                }
            });

            return result;
        }

        private void taskPositionModified(final V task) {
            executeWithPreAndPostActionsOnlyIfNewEntrance(new IAction() {
                @Override
                public void doAction() {
                    List<Recalculation> recalculationsNeededFrom = getRecalculationsNeededFrom(task);
                    doRecalculations(recalculationsNeededFrom, Collections.singletonList(task));
                }
            });
        }

        private void doRecalculations(List<Recalculation> recalculationsNeeded,
                Collection<? extends V> initiallyModified) {

            Set<V> allModified = new HashSet<>();
            allModified.addAll(initiallyModified);

            for (Recalculation each : recalculationsNeeded) {
                boolean modified = each.doRecalculation();
                if (modified) {
                    allModified.add(each.taskPoint.task);
                }
            }

            List<V> shrunkContainers = shrunkContainersOfModified(allModified);
            for (V each : getTaskAffectedByShrinking(shrunkContainers)) {
                doRecalculations(getRecalculationsNeededFrom(each), Collections.singletonList(each));
            }
        }

        private List<V> getTaskAffectedByShrinking(List<V> shrunkContainers) {
            List<V> tasksAffectedByShrinking = new ArrayList<>();
            for (V each : shrunkContainers) {

                for (D eachDependency : graph.outgoingEdgesOf(each)) {

                    boolean condition = adapter.getType(eachDependency) == DependencyType.START_START
                            && adapter.isVisible(eachDependency);

                    if (condition) {
                        tasksAffectedByShrinking.add(adapter.getDestination(eachDependency));
                    }
                }
            }

            return tasksAffectedByShrinking;
        }

        private List<V> shrunkContainersOfModified(Set<V> allModified) {
            Set<V> topmostToShrink = getTopMostThatCouldPotentiallyNeedShrinking(allModified);
            List<V> allToShrink = new ArrayList<>();

            for (V each : topmostToShrink) {
                allToShrink.addAll(getContainersBottomUp(each));
            }

            List<V> result = new ArrayList<>();
            for (V each : allToShrink) {
                boolean modified = enforceParentShrinkage(each);
                if (modified) {
                    result.add(each);
                }
            }

            return result;
        }

        private Set<V> getTopMostThatCouldPotentiallyNeedShrinking(Collection<V> modified) {
            Set<V> result = new HashSet<>();
            for (V each : modified) {
                V t = getTopmostFor(each);
                if (adapter.isContainer(t)) {
                    result.add(t);
                }
            }

            return result;
        }

        private Collection<? extends V> getContainersBottomUp(V container) {
            List<V> result = new ArrayList<V>();
            List<V> tasks = adapter.getChildren(container);
            for (V each : tasks) {
                if (adapter.isContainer(each)) {
                    result.addAll(getContainersBottomUp(each));
                    result.add(each);
                }
            }
            result.add(container);

            return result;
        }

        boolean enforceParentShrinkage(V container) {
            GanttDate oldBeginDate = adapter.getStartDate(container);
            GanttDate firstStart = getSmallestBeginDateFromChildrenFor(container);
            GanttDate lastEnd = getBiggestEndDateFromChildrenFor(container);
            GanttDate previousEnd = adapter.getEndDateFor(container);

            if (firstStart.after(oldBeginDate) || previousEnd.after(lastEnd)) {
                adapter.setStartDateFor(container, GanttDate.max(firstStart, oldBeginDate));
                adapter.setEndDateFor(container, GanttDate.min(lastEnd, previousEnd));
                return true;
            }

            return false;
        }
    }

    private GanttDate getSmallestBeginDateFromChildrenFor(V container) {
        return Collections.min(getChildrenDates(container, Point.START));
    }

    private GanttDate getBiggestEndDateFromChildrenFor(V container) {
        return Collections.max(getChildrenDates(container, Point.END));
    }

    private List<GanttDate> getChildrenDates(V container, Point point) {
        List<V> children = adapter.getChildren(container);
        List<GanttDate> result = new ArrayList<>();

        if (children.isEmpty()) {
            result.add(getDateFor(container, point));
        }

        for (V each : children) {
            result.add(getDateFor(each, point));
        }

        return result;
    }

    GanttDate getDateFor(V task, Point point) {
        if (point.equals(Point.START)) {
            return adapter.getStartDate(task);
        } else {
            return adapter.getEndDateFor(task);
        }
    }

    List<Recalculation> getRecalculationsNeededFrom(V task) {
        List<Recalculation> result = new ArrayList<>();
        Set<Recalculation> parentRecalculationsAlreadyDone = new HashSet<>();
        Recalculation first = recalculationFor(allPointsPotentiallyModified(task));
        first.couldHaveBeenModifiedBeforehand();

        result.addAll(getParentsRecalculations(parentRecalculationsAlreadyDone, first.taskPoint));
        result.add(first);

        Queue<Recalculation> pendingOfVisit = new LinkedList<>();
        pendingOfVisit.offer(first);

        Map<Recalculation, Recalculation> alreadyVisited = new HashMap<>();
        alreadyVisited.put(first, first);

        while (!pendingOfVisit.isEmpty()) {

            Recalculation current = pendingOfVisit.poll();

            for (TaskPoint each : current.taskPoint.getImmediateSuccessors()) {

                if (each.isImmediatelyDerivedFrom(current.taskPoint)) {
                    continue;
                }

                Recalculation recalculationToAdd = getRecalcualtionToAdd(each, alreadyVisited);
                recalculationToAdd.comesFromPredecessor(current);

                if (!alreadyVisited.containsKey(recalculationToAdd)) {
                    result.addAll(getParentsRecalculations(parentRecalculationsAlreadyDone, each));
                    result.add(recalculationToAdd);
                    pendingOfVisit.offer(recalculationToAdd);
                    alreadyVisited.put(recalculationToAdd, recalculationToAdd);
                }
            }
        }

        return topologicalSorter.sort(result);
    }

    private Recalculation getRecalcualtionToAdd(TaskPoint taskPoint,
            Map<Recalculation, Recalculation> alreadyVisited) {
        Recalculation result = recalculationFor(taskPoint);

        if (alreadyVisited.containsKey(result)) {
            return alreadyVisited.get(result);
        } else {
            return result;
        }
    }

    private List<Recalculation> getParentsRecalculations(Set<Recalculation> parentRecalculationsAlreadyDone,
            TaskPoint taskPoint) {
        List<Recalculation> result = new ArrayList<>();

        for (TaskPoint eachParent : parentsRecalculationsNeededFor(taskPoint)) {

            Recalculation parentRecalculation = parentRecalculation(eachParent.task);

            if (!parentRecalculationsAlreadyDone.contains(parentRecalculation)) {
                parentRecalculationsAlreadyDone.add(parentRecalculation);
                result.add(parentRecalculation);
            }
        }

        return result;
    }

    private Set<TaskPoint> parentsRecalculationsNeededFor(TaskPoint current) {
        Set<TaskPoint> result = new LinkedHashSet<>();

        if (current.areAllPointsPotentiallyModified()) {
            List<V> path = fromTaskToTop(current.task);

            if (path.size() > 1) {
                path = path.subList(1, path.size());
                Collections.reverse(path);
                result.addAll(asBothPoints(path));
            }
        }

        return result;
    }

    private Collection<? extends TaskPoint> asBothPoints(List<V> parents) {
        List<TaskPoint> result = new ArrayList<>();
        for (V each : parents) {
            result.add(allPointsPotentiallyModified(each));
        }

        return result;
    }

    private List<V> fromTaskToTop(V task) {
        List<V> result = new ArrayList<>();
        V current = task;

        while (current != null) {
            result.add(current);
            current = fromChildToParent.get(current);
        }

        return result;
    }

    private Recalculation parentRecalculation(V task) {
        return new Recalculation(allPointsPotentiallyModified(task), true);
    }

    private Recalculation recalculationFor(TaskPoint taskPoint) {
        return new Recalculation(taskPoint, false);
    }

    private class Recalculation {

        private final boolean parentRecalculation;

        private final TaskPoint taskPoint;

        private Set<Recalculation> recalculationsCouldAffectThis = new HashSet<>();

        private boolean recalculationCalled = false;

        private boolean dataPointModified = false;

        private boolean couldHaveBeenModifiedBeforehand = false;

        Recalculation(TaskPoint taskPoint, boolean isParentRecalculation) {
            Validate.notNull(taskPoint);
            this.taskPoint = taskPoint;
            this.parentRecalculation = isParentRecalculation;
        }

        public void couldHaveBeenModifiedBeforehand() {
            couldHaveBeenModifiedBeforehand = true;
        }

        public void comesFromPredecessor(Recalculation predecessor) {
            recalculationsCouldAffectThis.add(predecessor);
        }

        boolean doRecalculation() {
            recalculationCalled = true;
            dataPointModified = haveToDoCalculation() && taskChangesPosition();
            return dataPointModified;
        }

        private boolean haveToDoCalculation() {
            return recalculationsCouldAffectThis.isEmpty() || predecessorsHaveBeenModified();
        }

        private boolean predecessorsHaveBeenModified() {
            for (Recalculation each : recalculationsCouldAffectThis) {
                if (!each.recalculationCalled) {
                    // FIXME this need to be not commented, but some imported projects not working with it
                    //throw new RuntimeException("the predecessor must be called first");
                }
                if (each.dataPointModified || each.couldHaveBeenModifiedBeforehand) {
                    return true;
                }
            }
            return false;
        }

        private boolean taskChangesPosition() {
            ChangeTracker tracker = trackTaskChanges();
            Constraint.initialValue(noRestrictions()).withConstraints(getConstraintsToApply()).apply();
            return tracker.taskHasChanged();
        }

        @SuppressWarnings("unchecked")
        private List<Constraint<PositionRestrictions>> getConstraintsToApply() {

            Constraint<PositionRestrictions> weakForces = scheduleBackwards ? new WeakForwardForces()
                    : new WeakBackwardsForces();

            Constraint<PositionRestrictions> dominatingForces = scheduleBackwards ? new DominatingBackwardForces()
                    : new DominatingForwardForces();

            if (dependenciesConstraintsHavePriority) {
                return asList(weakForces, dominatingForces);
            } else {
                return asList(weakForces, dominatingForces, weakForces);
            }
        }

        abstract class PositionRestrictions {

            private final GanttDate start;

            private final GanttDate end;

            PositionRestrictions(GanttDate start, GanttDate end) {
                this.start = start;
                this.end = end;
            }

            GanttDate getStart() {
                return start;
            }

            GanttDate getEnd() {
                return end;
            }

            abstract List<Constraint<GanttDate>> getStartConstraints();

            abstract List<Constraint<GanttDate>> getEndConstraints();

            abstract boolean satisfies(PositionRestrictions other);

        }

        private final class NoRestrictions extends PositionRestrictions {

            public NoRestrictions(TaskPoint taskPoint) {
                super(adapter.getStartDate(taskPoint.task), adapter.getEndDateFor(taskPoint.task));
            }

            @Override
            List<Constraint<GanttDate>> getStartConstraints() {
                return Collections.emptyList();
            }

            @Override
            List<Constraint<GanttDate>> getEndConstraints() {
                return Collections.emptyList();
            }

            @Override
            boolean satisfies(PositionRestrictions restrictions) {
                return true;
            }

        }

        PositionRestrictions noRestrictions() {
            return new NoRestrictions(taskPoint);
        }

        DatesBasedPositionRestrictions biggerThan(GanttDate start, GanttDate end) {

            ComparisonType type = isScheduleForward() ? ComparisonType.BIGGER_OR_EQUAL_THAN
                    : ComparisonType.BIGGER_OR_EQUAL_THAN_LEFT_FLOATING;

            return new DatesBasedPositionRestrictions(type, start, end);
        }

        DatesBasedPositionRestrictions lessThan(GanttDate start, GanttDate end) {

            ComparisonType type = isScheduleForward() ? ComparisonType.LESS_OR_EQUAL_THAN_RIGHT_FLOATING
                    : ComparisonType.LESS_OR_EQUAL_THAN;

            return new DatesBasedPositionRestrictions(type, start, end);
        }

        class DatesBasedPositionRestrictions extends PositionRestrictions {

            private Constraint<GanttDate> startConstraint;
            private Constraint<GanttDate> endConstraint;

            public DatesBasedPositionRestrictions(ComparisonType comparisonType, GanttDate start, GanttDate end) {
                super(start, end);
                this.startConstraint = ConstraintOnComparableValues.instantiate(comparisonType, start);
                this.endConstraint = ConstraintOnComparableValues.instantiate(comparisonType, end);
            }

            boolean satisfies(PositionRestrictions other) {
                if (DatesBasedPositionRestrictions.class.isInstance(other)) {
                    return satisfies(DatesBasedPositionRestrictions.class.cast(other));
                }

                return false;
            }

            private boolean satisfies(DatesBasedPositionRestrictions other) {
                return startConstraint.isSatisfiedBy(other.getStart())
                        && endConstraint.isSatisfiedBy(other.getEnd());
            }

            @Override
            List<Constraint<GanttDate>> getStartConstraints() {
                return Collections.singletonList(startConstraint);
            }

            @Override
            List<Constraint<GanttDate>> getEndConstraints() {
                return Collections.singletonList(endConstraint);
            }

        }

        class ChangeTracker {
            private GanttDate start;
            private GanttDate end;
            private final V task;

            public ChangeTracker(V task) {
                this.task = task;
                this.start = adapter.getStartDate(task);
                this.end = adapter.getEndDateFor(task);
            }

            public boolean taskHasChanged() {
                return areNotEqual(adapter.getStartDate(task), this.start)
                        || areNotEqual(adapter.getEndDateFor(task), this.end);
            }

        }

        boolean areNotEqual(GanttDate a, GanttDate b) {
            return a != b && a.compareTo(b) != 0;
        }

        protected ChangeTracker trackTaskChanges() {
            return new ChangeTracker(taskPoint.task);
        }

        abstract class Forces extends Constraint<PositionRestrictions> {

            protected final V task;

            public Forces() {
                this.task = taskPoint.task;
            }

            private PositionRestrictions resultingRestrictions = noRestrictions();

            protected PositionRestrictions applyConstraintTo(PositionRestrictions restrictions) {
                if (adapter.isFixed(task)) {
                    return restrictions;
                }

                resultingRestrictions = enforceUsingPreviousRestrictions(restrictions);
                return resultingRestrictions;
            }

            public boolean isSatisfiedBy(PositionRestrictions value) {
                return resultingRestrictions.satisfies(value);
            }

            public void checkSatisfiesResult(PositionRestrictions finalResult) {
                super.checkSatisfiesResult(finalResult);
                checkStartConstraints(finalResult.getStart());
                checkEndConstraints(finalResult.getEnd());
            }

            private void checkStartConstraints(GanttDate finalStart) {
                Constraint.checkSatisfyResult(getStartConstraints(), finalStart);
            }

            private void checkEndConstraints(GanttDate finalEnd) {
                Constraint.checkSatisfyResult(getEndConstraints(), finalEnd);
            }

            abstract List<Constraint<GanttDate>> getStartConstraints();

            abstract List<Constraint<GanttDate>> getEndConstraints();

            abstract PositionRestrictions enforceUsingPreviousRestrictions(PositionRestrictions restrictions);
        }

        abstract class Dominating extends Forces {

            private final Point primary;
            private final Point secondary;

            public Dominating(Point primary, Point secondary) {
                Validate.isTrue(isSupportedPoint(primary));
                Validate.isTrue(isSupportedPoint(secondary));
                Validate.isTrue(!primary.equals(secondary));

                this.primary = primary;
                this.secondary = secondary;
            }

            private boolean isSupportedPoint(Point point) {
                EnumSet<Point> validPoints = EnumSet.of(Point.START, Point.END);
                return validPoints.contains(point);
            }

            private Point getPrimaryPoint() {
                return primary;
            }

            private Point getSecondaryPoint() {
                return secondary;
            }

            @Override
            PositionRestrictions enforceUsingPreviousRestrictions(PositionRestrictions restrictions) {
                if (parentRecalculation) {
                    // avoid interference from task containers shrinking
                    return enforcePrimaryPoint(restrictions);
                } else if (taskPoint.areAllPointsPotentiallyModified()) {
                    return enforceBoth(restrictions);
                } else if (taskPoint.somePointPotentiallyModified()) {
                    return enforceSecondaryPoint(restrictions);
                }

                return restrictions;
            }

            private PositionRestrictions enforceBoth(PositionRestrictions restrictions) {
                ChangeTracker changeTracker = trackTaskChanges();
                PositionRestrictions currentRestrictions = enforcePrimaryPoint(restrictions);

                if (changeTracker.taskHasChanged() || parentRecalculation || couldHaveBeenModifiedBeforehand) {
                    return enforceSecondaryPoint(currentRestrictions);
                }

                return currentRestrictions;
            }

            private PositionRestrictions enforcePrimaryPoint(PositionRestrictions originalRestrictions) {
                GanttDate newDominatingPointDate = calculatePrimaryPointDate(originalRestrictions);
                return enforceRestrictionsFor(primary, newDominatingPointDate);
            }

            /**
             * Calculates the new date for the primary point based on the
             * present constraints. If there are no constraints this method will
             * return the existent commanding point date
             * @param originalRestrictions
             */
            private GanttDate calculatePrimaryPointDate(PositionRestrictions originalRestrictions) {

                GanttDate newDate = Constraint.<GanttDate>initialValue(null)
                        .withConstraints(getConstraintsFrom(originalRestrictions, getPrimaryPoint()))
                        .withConstraints(getConstraintsFor(getPrimaryPoint())).applyWithoutFinalCheck();

                if (newDate == null) {
                    return getTaskDateFor(getPrimaryPoint());
                }

                return newDate;
            }

            private List<Constraint<GanttDate>> getConstraintsFor(Point point) {
                Validate.isTrue(isSupportedPoint(point));
                switch (point) {

                case START:
                    return getStartConstraints();

                case END:
                    return getEndConstraints();

                default:
                    throw new RuntimeException("shouldn't happen");
                }
            }

            private PositionRestrictions enforceSecondaryPoint(PositionRestrictions restrictions) {
                GanttDate newSecondaryPointDate = calculateSecondaryPointDate(restrictions);
                if (newSecondaryPointDate == null) {
                    return restrictions;
                }

                restrictions = enforceRestrictionsFor(getSecondaryPoint(), newSecondaryPointDate);

                if (taskPoint.onlyModifies(getSecondaryPoint())) {

                    // primary point constraints could be the ones "commanding" now
                    GanttDate potentialPrimaryDate = calculatePrimaryPointDate(restrictions);

                    if (!doSatisfyOrderCondition(potentialPrimaryDate, getTaskDateFor(getPrimaryPoint()))) {
                        return enforceRestrictionsFor(getPrimaryPoint(), potentialPrimaryDate);
                    }
                }

                return restrictions;
            }

            private GanttDate calculateSecondaryPointDate(PositionRestrictions restrictions) {

                GanttDate newEnd = Constraint.<GanttDate>initialValue(null)
                        .withConstraints(getConstraintsFrom(restrictions, getSecondaryPoint()))
                        .withConstraints(getConstraintsFor(getSecondaryPoint())).applyWithoutFinalCheck();

                return newEnd;
            }

            protected abstract boolean doSatisfyOrderCondition(GanttDate supposedlyBefore,
                    GanttDate supposedlyAfter);

            private PositionRestrictions enforceRestrictionsFor(Point point, GanttDate newDate) {
                GanttDate old = getTaskDateFor(point);
                if (areNotEqual(old, newDate)) {
                    setTaskDateFor(point, newDate);
                }
                return createRestrictionsFor(getTaskDateFor(Point.START), getTaskDateFor(Point.END));
            }

            GanttDate getTaskDateFor(Point point) {
                Validate.isTrue(isSupportedPoint(point));
                return getDateFor(task, point);
            }

            protected abstract PositionRestrictions createRestrictionsFor(GanttDate start, GanttDate end);

            private void setTaskDateFor(Point point, GanttDate date) {
                Validate.isTrue(isSupportedPoint(point));
                switch (point) {

                case START:
                    adapter.setStartDateFor(task, date);
                    break;

                case END:
                    adapter.setEndDateFor(task, date);
                }
            }

            private List<Constraint<GanttDate>> getConstraintsFrom(PositionRestrictions restrictions, Point point) {
                Validate.isTrue(isSupportedPoint(point));
                switch (point) {

                case START:
                    return restrictions.getStartConstraints();

                case END:
                    return restrictions.getEndConstraints();

                default:
                    throw new RuntimeException("shouldn't happen");
                }
            }

            protected List<Constraint<GanttDate>> getConstraintsForPrimaryPoint() {
                List<Constraint<GanttDate>> result = new ArrayList<>();

                if (dependenciesConstraintsHavePriority) {
                    result.addAll(getTaskConstraints(getPrimaryPoint()));
                    result.addAll(getDependenciesConstraintsFor(getPrimaryPoint()));
                } else {
                    result.addAll(getDependenciesConstraintsFor(getPrimaryPoint()));
                    result.addAll(getTaskConstraints(getPrimaryPoint()));
                }

                result.addAll(getGlobalConstraintsToApply(getPrimaryPoint()));

                return result;
            }

            private Collection<Constraint<GanttDate>> getGlobalConstraintsToApply(Point point) {
                Validate.isTrue(isSupportedPoint(point));
                switch (point) {

                case START:
                    return globalStartConstraints;

                case END:
                    return globalEndConstraints;

                default:
                    throw new RuntimeException("shouldn't happen");
                }
            }

            protected List<Constraint<GanttDate>> getConstraintsForSecondaryPoint() {
                return getDependenciesConstraintsFor(getSecondaryPoint());
            }

            private List<Constraint<GanttDate>> getDependenciesConstraintsFor(Point point) {
                final Set<D> withDependencies = getDependenciesAffectingThisTask();
                return adapter.getConstraints(getCalculator(), withDependencies, point);
            }

            protected abstract Set<D> getDependenciesAffectingThisTask();

            private List<Constraint<GanttDate>> getTaskConstraints(Point point) {
                Validate.isTrue(isSupportedPoint(point));
                switch (point) {

                case START:
                    return adapter.getStartConstraintsFor(task);

                case END:
                    return adapter.getEndConstraintsFor(task);

                default:
                    throw new RuntimeException("shouldn't happen");
                }
            }

            protected abstract ConstraintCalculator<V> getCalculator();

            protected ConstraintCalculator<V> createNormalCalculator() {
                return createCalculator(false);
            }

            protected ConstraintCalculator<V> createBackwardsCalculator() {
                return createCalculator(true);
            }

            private ConstraintCalculator<V> createCalculator(boolean inverse) {
                return new ConstraintCalculator<V>(inverse) {

                    @Override
                    protected GanttDate getStartDate(V vertex) {
                        return adapter.getStartDate(vertex);
                    }

                    @Override
                    protected GanttDate getEndDate(V vertex) {
                        return adapter.getEndDateFor(vertex);
                    }
                };
            }

        }

        class DominatingForwardForces extends Dominating {

            public DominatingForwardForces() {
                super(Point.START, Point.END);
            }

            @Override
            List<Constraint<GanttDate>> getStartConstraints() {
                return getConstraintsForPrimaryPoint();
            }

            @Override
            List<Constraint<GanttDate>> getEndConstraints() {
                return getConstraintsForSecondaryPoint();
            }

            @Override
            protected Set<D> getDependenciesAffectingThisTask() {
                return graph.incomingEdgesOf(task);
            }

            @Override
            protected ConstraintCalculator<V> getCalculator() {
                return createNormalCalculator();
            }

            @Override
            protected PositionRestrictions createRestrictionsFor(GanttDate start, GanttDate end) {
                return biggerThan(start, end);
            }

            @Override
            protected boolean doSatisfyOrderCondition(GanttDate supposedlyBefore, GanttDate supposedlyAfter) {
                return supposedlyBefore.compareTo(supposedlyAfter) <= 0;
            }

        }

        class DominatingBackwardForces extends Dominating {

            public DominatingBackwardForces() {
                super(Point.END, Point.START);
            }

            @Override
            List<Constraint<GanttDate>> getStartConstraints() {
                return getConstraintsForSecondaryPoint();
            }

            @Override
            List<Constraint<GanttDate>> getEndConstraints() {
                return getConstraintsForPrimaryPoint();
            }

            @Override
            protected Set<D> getDependenciesAffectingThisTask() {
                return graph.outgoingEdgesOf(task);
            }

            @Override
            protected ConstraintCalculator<V> getCalculator() {
                return createBackwardsCalculator();
            }

            @Override
            protected PositionRestrictions createRestrictionsFor(GanttDate start, GanttDate end) {
                return lessThan(start, end);
            }

            @Override
            protected boolean doSatisfyOrderCondition(GanttDate supposedlyBefore, GanttDate supposedlyAfter) {
                return supposedlyBefore.compareTo(supposedlyAfter) >= 0;
            }

        }

        class WeakForwardForces extends Forces {

            @Override
            List<Constraint<GanttDate>> getStartConstraints() {
                return adapter.getStartConstraintsFor(task);
            }

            @Override
            List<Constraint<GanttDate>> getEndConstraints() {
                return Collections.emptyList();
            }

            @Override
            PositionRestrictions enforceUsingPreviousRestrictions(PositionRestrictions restrictions) {

                GanttDate result = Constraint.<GanttDate>initialValue(null)
                        .withConstraints(restrictions.getStartConstraints()).withConstraints(getStartConstraints())
                        .applyWithoutFinalCheck();

                if (result != null) {
                    enforceRestrictions(result);
                    return biggerThan(result, adapter.getEndDateFor(task));
                }

                return restrictions;
            }

            private void enforceRestrictions(GanttDate result) {
                if (!result.equals(getStartDate(task))) {
                    adapter.setStartDateFor(task, result);
                }
            }

        }

        class WeakBackwardsForces extends Forces {

            @Override
            PositionRestrictions enforceUsingPreviousRestrictions(PositionRestrictions restrictions) {

                GanttDate result = Constraint.<GanttDate>initialValue(null)
                        .withConstraints(restrictions.getEndConstraints()).withConstraints(getEndConstraints())
                        .applyWithoutFinalCheck();

                if (result != null) {
                    enforceRestrictions(result);
                    return lessThan(adapter.getStartDate(task), result);
                }

                return restrictions;
            }

            @Override
            List<Constraint<GanttDate>> getStartConstraints() {
                return Collections.emptyList();
            }

            @Override
            List<Constraint<GanttDate>> getEndConstraints() {
                return adapter.getEndConstraintsFor(task);
            }

            private void enforceRestrictions(GanttDate newEnd) {
                if (!newEnd.equals(getEndDateFor(task))) {
                    adapter.setEndDateFor(task, newEnd);
                }
            }
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder().append(parentRecalculation).append(taskPoint).toHashCode();
        }

        @Override
        public String toString() {
            return String.format("%s, parentRecalculation: %s, predecessors: %s", taskPoint, parentRecalculation,
                    asSimpleString(recalculationsCouldAffectThis));
        }

        private String asSimpleString(Collection<? extends Recalculation> recalculations) {
            StringBuilder result = new StringBuilder();
            result.append("[");

            for (Recalculation each : recalculations) {
                result.append(each.taskPoint).append(", ");
            }
            result.append("]");

            return result.toString();
        }

        @Override
        public boolean equals(Object obj) {
            if (Recalculation.class.isInstance(obj)) {
                Recalculation other = Recalculation.class.cast(obj);

                return new EqualsBuilder().append(parentRecalculation, other.parentRecalculation)
                        .append(taskPoint, other.taskPoint).isEquals();
            }

            return false;
        }
    }

    public void remove(final V task) {
        Set<V> needingEnforcing = getOutgoingTasksFor(task);
        graph.removeVertex(task);
        topLevelTasks.remove(task);
        fromChildToParent.remove(task);

        if (adapter.isContainer(task)) {
            for (V t : adapter.getChildren(task)) {
                remove(t);
            }
        }

        topologicalSorter.recalculationNeeded();
        enforcer.enforceRestrictionsOn(needingEnforcing);
    }

    public void removeDependency(D dependency) {
        graph.removeEdge(dependency);
        topologicalSorter.recalculationNeeded();
        V destination = adapter.getDestination(dependency);
        V source = adapter.getSource(dependency);
        enforcer.enforceRestrictionsOn(destination);
        enforcer.enforceRestrictionsOn(source);
    }

    public boolean canAddDependency(D dependency) {
        return !isForbidden(dependency) && doesNotProvokeLoop(dependency);
    }

    private boolean isForbidden(D dependency) {
        if (!adapter.isVisible(dependency)) {
            // the invisible dependencies, the ones used to implement container behavior are not forbidden
            return false;
        }

        boolean endEndDependency = DependencyType.END_END == dependency.getType();
        boolean startStartDependency = DependencyType.START_START == dependency.getType();

        V source = adapter.getSource(dependency);
        V destination = adapter.getDestination(dependency);
        boolean destinationIsContainer = adapter.isContainer(destination);
        boolean sourceIsContainer = adapter.isContainer(source);

        return (destinationIsContainer && endEndDependency) || (sourceIsContainer && startStartDependency);
    }

    public void add(D dependency) {
        add(dependency, true);
    }

    public void addWithoutEnforcingConstraints(D dependency) {
        add(dependency, false);
    }

    private void add(D dependency, boolean enforceRestrictions) {
        if (isForbidden(dependency)) {
            return;
        }

        V source = adapter.getSource(dependency);
        V destination = adapter.getDestination(dependency);
        graph.addEdge(source, destination, dependency);
        topologicalSorter.recalculationNeeded();
        if (enforceRestrictions) {
            enforceRestrictions(destination);
        }
    }

    public void enforceRestrictions(final V task) {
        enforcer.taskPositionModified(task);
    }

    public DeferedNotifier manualNotificationOn(IAction action) {
        return enforcer.manualNotification(action);
    }

    public boolean contains(D dependency) {
        return graph.containsEdge(dependency);
    }

    public List<V> getTasks() {
        return new ArrayList<>(graph.vertexSet());
    }

    public List<D> getVisibleDependencies() {
        ArrayList<D> result = new ArrayList<>();
        for (D dependency : graph.edgeSet()) {
            if (adapter.isVisible(dependency)) {
                result.add(dependency);
            }
        }

        return result;
    }

    public List<V> getTopLevelTasks() {
        return Collections.unmodifiableList(topLevelTasks);
    }

    public void childrenAddedTo(V task) {
        enforcer.enforceRestrictionsOn(task);
    }

    public List<V> getInitialTasks() {
        List<V> result = new ArrayList<>();
        for (V task : graph.vertexSet()) {
            int dependencies = graph.inDegreeOf(task);

            if ((dependencies == 0)
                    || (dependencies == getNumberOfIncomingDependenciesByType(task, DependencyType.END_END))) {

                result.add(task);
            }
        }

        return result;
    }

    public IDependency<V> getDependencyFrom(V from, V to) {
        return graph.getEdge(from, to);
    }

    public Set<V> getOutgoingTasksFor(V task) {
        Set<V> result = new HashSet<>();
        for (D dependency : graph.outgoingEdgesOf(task)) {
            result.add(adapter.getDestination(dependency));
        }

        return result;
    }

    public Set<V> getIncomingTasksFor(V task) {
        Set<V> result = new HashSet<>();
        for (D dependency : graph.incomingEdgesOf(task)) {
            result.add(adapter.getSource(dependency));
        }

        return result;
    }

    public boolean hasVisibleIncomingDependencies(V task) {
        return isSomeVisibleAndNotEndEnd(graph.incomingEdgesOf(task));
    }

    private boolean isSomeVisibleAndNotEndEnd(Set<D> dependencies) {
        for (D each : dependencies) {
            if (!each.getType().equals(DependencyType.END_END) && adapter.isVisible(each)) {
                return true;
            }
        }

        return false;
    }

    public boolean hasVisibleOutcomingDependencies(V task) {
        return isSomeVisibleAndNotStartStart(graph.outgoingEdgesOf(task));
    }

    private boolean isSomeVisibleAndNotStartStart(Set<D> dependencies) {
        for (D each : dependencies) {
            if (!each.getType().equals(DependencyType.START_START) && adapter.isVisible(each)) {
                return true;
            }
        }

        return false;
    }

    public List<V> getLatestTasks() {
        List<V> tasks = new ArrayList<>();

        for (V task : graph.vertexSet()) {
            int dependencies = graph.outDegreeOf(task);

            if ((dependencies == 0)
                    || (dependencies == getNumberOfOutgoingDependenciesByType(task, DependencyType.START_START))) {

                tasks.add(task);
            }
        }

        return tasks;
    }

    private int getNumberOfIncomingDependenciesByType(V task, DependencyType dependencyType) {
        int count = 0;
        for (D dependency : graph.incomingEdgesOf(task)) {
            if (adapter.getType(dependency).equals(dependencyType)) {
                count++;
            }
        }

        return count;
    }

    private int getNumberOfOutgoingDependenciesByType(V task, DependencyType dependencyType) {
        int count = 0;
        for (D dependency : graph.outgoingEdgesOf(task)) {
            if (adapter.getType(dependency).equals(dependencyType)) {
                count++;
            }
        }

        return count;
    }

    public boolean isContainer(V task) {
        if (task == null) {
            return false;
        }

        return adapter.isContainer(task);
    }

    public boolean contains(V container, V task) {
        if ((container == null) || (task == null)) {
            return false;
        }

        if (adapter.isContainer(container)) {
            return adapter.getChildren(container).contains(task);
        }

        return false;
    }

    public boolean doesNotProvokeLoop(D dependency) {
        Set<TaskPoint> reachableFromDestination = destinationPoint(dependency).getReachable();
        for (TaskPoint each : reachableFromDestination) {

            if (each.sendsModificationsThrough(dependency)) {
                return false;
            }
        }

        return true;
    }

    TaskPoint destinationPoint(D dependency) {
        V destination = getDependencyDestination(dependency);
        return new TaskPoint(destination, getDestinationPoint(dependency.getType()));
    }

    private Point getDestinationPoint(DependencyType type) {
        return type.getSourceAndDestination()[isScheduleForward() ? 1 : 0];
    }

    TaskPoint sourcePoint(D dependency) {
        V source = getDependencySource(dependency);
        return new TaskPoint(source, getSourcePoint(dependency.getType()));
    }

    /**
     * The dominating point is the one that causes the other point to be
     * modified; e.g. when doing forward scheduling the dominating point is the
     * start.
     */
    private boolean isDominatingPoint(Point point) {
        return point == getDominatingPoint();
    }

    private Point getDominatingPoint() {
        return isScheduleForward() ? Point.START : Point.END;
    }

    private Point getSourcePoint(DependencyType type) {
        return type.getSourceAndDestination()[isScheduleForward() ? 0 : 1];
    }

    private V getDependencySource(D dependency) {
        return isScheduleForward() ? adapter.getSource(dependency) : adapter.getDestination(dependency);
    }

    private V getDependencyDestination(D dependency) {
        return isScheduleForward() ? adapter.getDestination(dependency) : adapter.getSource(dependency);
    }

    TaskPoint allPointsPotentiallyModified(V task) {
        return new TaskPoint(task, getDominatingPoint());
    }

    private class TaskPoint {

        private final V task;

        private final boolean isContainer;

        private final Set<Point> pointsModified;

        private final Point entryPoint;

        TaskPoint(V task, Point entryPoint) {
            Validate.notNull(task);
            Validate.notNull(entryPoint);
            this.task = task;
            this.entryPoint = entryPoint;

            this.pointsModified = isDominatingPoint(entryPoint) ? EnumSet.of(Point.START, Point.END)
                    : EnumSet.of(entryPoint);

            this.isContainer = adapter.isContainer(task);
        }

        @Override
        public String toString() {
            return String.format("%s(%s)", task, pointsModified);
        }

        @Override
        public boolean equals(Object obj) {
            if (TaskPoint.class.isInstance(obj)) {
                TaskPoint other = TaskPoint.class.cast(obj);

                return new EqualsBuilder().append(task, other.task).append(pointsModified, other.pointsModified)
                        .isEquals();
            }

            return false;
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder().append(task).append(pointsModified).toHashCode();
        }

        public boolean areAllPointsPotentiallyModified() {
            return pointsModified.size() > 1;
        }

        public boolean somePointPotentiallyModified() {
            return pointsModified.contains(Point.START) || pointsModified.contains(Point.END);
        }

        public boolean onlyModifies(Point point) {
            return pointsModified.size() == 1 && pointsModified.contains(point);
        }

        Set<TaskPoint> getReachable() {
            Set<TaskPoint> result = new HashSet<>();
            Queue<TaskPoint> pending = new LinkedList<>();
            result.add(this);
            pending.offer(this);

            while (!pending.isEmpty()) {
                TaskPoint current = pending.poll();
                Set<TaskPoint> immendiate = current.getImmediateSuccessors();

                for (TaskPoint each : immendiate) {
                    if (!result.contains(each)) {
                        result.add(each);
                        pending.offer(each);
                    }
                }
            }

            return result;
        }

        public boolean isImmediatelyDerivedFrom(TaskPoint other) {
            return this.task.equals(other.task) && other.pointsModified.containsAll(this.pointsModified);
        }

        private Set<TaskPoint> cachedInmmediateSuccesors = null;

        public Set<TaskPoint> getImmediateSuccessors() {
            if (cachedInmmediateSuccesors != null) {
                return cachedInmmediateSuccesors;
            }

            Set<TaskPoint> result = new HashSet<>();
            result.addAll(getImmediatelyDerivedOnSameTask());

            Set<D> candidates = immediateDependencies();
            for (D each : candidates) {
                if (this.sendsModificationsThrough(each)) {
                    result.add(destinationPoint(each));
                }
            }

            return cachedInmmediateSuccesors = Collections.unmodifiableSet(result);
        }

        private Set<TaskPoint> cachedImmediatePredecessors = null;

        public Set<TaskPoint> getImmediatePredecessors() {
            if (cachedImmediatePredecessors != null) {
                return cachedImmediatePredecessors;
            }

            Set<TaskPoint> result = new HashSet<>();
            if (!isDominatingPoint(entryPoint)) {
                TaskPoint dominating = allPointsPotentiallyModified(task);
                assert isDominatingPoint(dominating.entryPoint);
                assert this.isImmediatelyDerivedFrom(dominating);
                result.add(dominating);
            }

            for (D each : immediateIncomingDependencies()) {
                if (this.receivesModificationsThrough(each)) {
                    TaskPoint sourcePoint = sourcePoint(each);
                    result.add(sourcePoint);
                }
            }

            return cachedImmediatePredecessors = Collections.unmodifiableSet(result);
        }

        private Collection<TaskPoint> getImmediatelyDerivedOnSameTask() {
            for (Point each : pointsModified) {
                if (isDominatingPoint(each)) {
                    return Collections.singletonList(new TaskPoint(task, each.getOther()));
                }
            }

            return Collections.emptyList();
        }

        private Set<D> immediateDependencies() {
            return isScheduleForward() ? graph.outgoingEdgesOf(this.task) : graph.incomingEdgesOf(this.task);
        }

        private Set<D> immediateIncomingDependencies() {
            return isScheduleForward() ? graph.incomingEdgesOf(this.task) : graph.outgoingEdgesOf(this.task);
        }

        public boolean sendsModificationsThrough(D dependency) {
            V source = getDependencySource(dependency);
            Point dependencySourcePoint = getSourcePoint(adapter.getType(dependency));

            return source.equals(task) && (!isContainer || pointsModified.contains(dependencySourcePoint));
        }

        private Point getSourcePoint(DependencyType type) {
            Point[] sourceAndDestination = type.getSourceAndDestination();

            return sourceAndDestination[isScheduleForward() ? 0 : 1];
        }

        private boolean receivesModificationsThrough(D dependency) {
            V destination = getDependencyDestination(dependency);
            Point destinationPoint = getDestinationPoint(adapter.getType(dependency));

            return destination.equals(task) && entryPoint == destinationPoint;
        }

    }

    private V getTopmostFor(V task) {
        V result = task;
        while (fromChildToParent.containsKey(result)) {
            result = fromChildToParent.get(result);
        }

        return result;
    }

    public boolean isScheduleForward() {
        return !isScheduleBackwards();
    }

    public boolean isScheduleBackwards() {
        return scheduleBackwards;
    }

    @Override
    public GanttDate getEndDateFor(V task) {
        return adapter.getEndDateFor(task);
    }

    @Override
    public List<Constraint<GanttDate>> getStartConstraintsFor(V task) {
        return adapter.getStartConstraintsFor(task);
    }

    @Override
    public List<Constraint<GanttDate>> getEndConstraintsFor(V task) {
        return adapter.getEndConstraintsFor(task);
    }

    @Override
    public GanttDate getStartDate(V task) {
        return adapter.getStartDate(task);
    }

    @Override
    public List<V> getChildren(V task) {
        if (!isContainer(task)) {
            return Collections.emptyList();
        }

        return adapter.getChildren(task);
    }

}