/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico 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.libreplan.web.limitingresources; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; 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.jgrapht.DirectedGraph; import org.jgrapht.alg.CycleDetector; import org.jgrapht.graph.DirectedMultigraph; import org.jgrapht.graph.SimpleDirectedGraph; import org.jgrapht.traverse.TopologicalOrderIterator; import org.libreplan.business.common.BaseEntity; import org.libreplan.business.planner.entities.GenericResourceAllocation; import org.libreplan.business.planner.entities.ResourceAllocation; import org.libreplan.business.planner.entities.SpecificResourceAllocation; import org.libreplan.business.planner.limiting.entities.DateAndHour; import org.libreplan.business.planner.limiting.entities.Gap; import org.libreplan.business.planner.limiting.entities.Gap.GapOnQueue; import org.libreplan.business.planner.limiting.entities.Gap.GapOnQueueWithQueueElement; import org.libreplan.business.planner.limiting.entities.InsertionRequirements; import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueDependency; import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueDependency.QueueDependencyType; import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement; import org.libreplan.business.resources.entities.Criterion; import org.libreplan.business.resources.entities.LimitingResourceQueue; import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.ResourceEnum; /** * @author Óscar González Fernández <ogonzalez@igalia.com> */ public class QueuesState { private final List<LimitingResourceQueue> queues; private final List<LimitingResourceQueueElement> unassignedElements; private final DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> graph; private final Map<Long, LimitingResourceQueue> queuesById; private final Map<Long, LimitingResourceQueueElement> elementsById; private final Map<Long, LimitingResourceQueue> queuesByResourceId; private static <T extends BaseEntity> Map<Long, T> byId(Collection<? extends T> entities) { Map<Long, T> result = new HashMap<>(); for (T each : entities) { result.put(each.getId(), each); } return result; } private static Map<Long, LimitingResourceQueue> byResourceId( Collection<? extends LimitingResourceQueue> limitingResourceQueues) { Map<Long, LimitingResourceQueue> result = new HashMap<>(); for (LimitingResourceQueue each : limitingResourceQueues) { result.put(each.getResource().getId(), each); } return result; } public QueuesState( List<LimitingResourceQueue> limitingResourceQueues, List<LimitingResourceQueueElement> unassignedLimitingResourceQueueElements) { this.queues = new ArrayList<>(limitingResourceQueues); this.unassignedElements = new ArrayList<>(unassignedLimitingResourceQueueElements); this.queuesById = byId(queues); this.elementsById = byId(allElements(limitingResourceQueues, unassignedLimitingResourceQueueElements)); this.queuesByResourceId = byResourceId(limitingResourceQueues); this.graph = buildGraph(getAllElements(unassignedElements, queues)); } private static DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> buildGraph( List<LimitingResourceQueueElement> allElements) { DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> result = instantiateDirectedGraph(); for (LimitingResourceQueueElement each : allElements) { result.addVertex(each); } for (LimitingResourceQueueElement each : allElements) { Set<LimitingResourceQueueDependency> dependenciesAsOrigin = each.getDependenciesAsOrigin(); for (LimitingResourceQueueDependency eachDependency : dependenciesAsOrigin) { addDependency(result, eachDependency); } } return result; } private static SimpleDirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> instantiateDirectedGraph() { return new SimpleDirectedGraph<>(LimitingResourceQueueDependency.class); } private static void addDependency( DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> result, LimitingResourceQueueDependency dependency) { LimitingResourceQueueElement origin = dependency.getHasAsOrigin(); LimitingResourceQueueElement destination = dependency.getHasAsDestiny(); result.addVertex(origin); result.addVertex(destination); result.addEdge(origin, destination, dependency); } private static List<LimitingResourceQueueElement> getAllElements( List<LimitingResourceQueueElement> unassigned, List<LimitingResourceQueue> queues) { List<LimitingResourceQueueElement> result = new ArrayList<>(); result.addAll(unassigned); for (LimitingResourceQueue each : queues) { result.addAll(each.getLimitingResourceQueueElements()); } return result; } private List<LimitingResourceQueueElement> allElements( List<LimitingResourceQueue> queues, List<LimitingResourceQueueElement> unassigned) { List<LimitingResourceQueueElement> result = new ArrayList<>(); for (LimitingResourceQueue each : queues) { result.addAll(each.getLimitingResourceQueueElements()); } result.addAll(unassigned); return result; } public List<LimitingResourceQueue> getQueues() { return Collections.unmodifiableList(queues); } public ArrayList<LimitingResourceQueue> getQueuesOrderedByResourceName() { ArrayList<LimitingResourceQueue> result = new ArrayList<>(queues); Collections.sort(result, new Comparator<LimitingResourceQueue>() { @Override public int compare(LimitingResourceQueue arg0, LimitingResourceQueue arg1) { if ( (arg0 == null) || (arg0.getResource().getName() == null) ) { return -1; } if ( (arg1 == null) || (arg1.getResource().getName() == null) ) { return 1; } return (arg0.getResource().getName().toLowerCase() .compareTo(arg1.getResource().getName().toLowerCase())); } }); return result; } public List<LimitingResourceQueueElement> getUnassigned() { return Collections.unmodifiableList(unassignedElements); } public void assignedToQueue(LimitingResourceQueueElement element, LimitingResourceQueue queue) { Validate.isTrue(unassignedElements.contains(element)); queue.addLimitingResourceQueueElement(element); unassignedElements.remove(element); } public LimitingResourceQueue getEquivalent(LimitingResourceQueue queue) { return queuesById.get(queue.getId()); } public LimitingResourceQueueElement getEquivalent(LimitingResourceQueueElement element) { return elementsById.get(element.getId()); } public void unassingFromQueue(LimitingResourceQueueElement externalElement) { LimitingResourceQueueElement queueElement = getEquivalent(externalElement); LimitingResourceQueue queue = queueElement.getLimitingResourceQueue(); if ( queue != null ) { queue.removeLimitingResourceQueueElement(queueElement); unassignedElements.add(queueElement); } } public void removeUnassigned(LimitingResourceQueueElement queueElement) { unassignedElements.remove(queueElement); } public List<LimitingResourceQueue> getAssignableQueues(LimitingResourceQueueElement element) { final ResourceAllocation<?> resourceAllocation = element.getResourceAllocation(); if ( resourceAllocation instanceof SpecificResourceAllocation ) { LimitingResourceQueue queue = getQueueFor(element.getResource()); Validate.notNull(queue); return Collections.singletonList(queue); } else if ( resourceAllocation instanceof GenericResourceAllocation ) { final GenericResourceAllocation generic = (GenericResourceAllocation) element.getResourceAllocation(); return findQueuesMatchingCriteria(generic); } throw new RuntimeException("unexpected type of: " + resourceAllocation); } private LimitingResourceQueue getQueueFor(Resource resource) { return queuesByResourceId.get(resource.getId()); } public InsertionRequirements getRequirementsFor(LimitingResourceQueueElement element) { List<LimitingResourceQueueDependency> dependenciesStart = new ArrayList<>(); List<LimitingResourceQueueDependency> dependenciesEnd = new ArrayList<>(); fillIncoming(element, dependenciesStart, dependenciesEnd); return InsertionRequirements.forElement(getEquivalent(element), dependenciesStart, dependenciesEnd); } public InsertionRequirements getRequirementsFor(LimitingResourceQueueElement element, DateAndHour startAt) { List<LimitingResourceQueueDependency> dependenciesStart = new ArrayList<>(); List<LimitingResourceQueueDependency> dependenciesEnd = new ArrayList<>(); fillIncoming(element, dependenciesStart, dependenciesEnd); return InsertionRequirements.forElement(getEquivalent(element), dependenciesStart, dependenciesEnd, startAt); } private void fillIncoming(LimitingResourceQueueElement element, List<LimitingResourceQueueDependency> dependenciesStart, List<LimitingResourceQueueDependency> dependenciesEnd) { Set<LimitingResourceQueueDependency> incoming = graph.incomingEdgesOf(element); for (LimitingResourceQueueDependency each : incoming) { List<LimitingResourceQueueDependency> addingTo = each.modifiesDestinationStart() ? dependenciesStart : dependenciesEnd; if ( each.isOriginNotDetached() ) { addingTo.add(each); } else { fillIncoming(each, addingTo); } } } private void fillIncoming(LimitingResourceQueueDependency next, List<LimitingResourceQueueDependency> result) { Set<LimitingResourceQueueDependency> incoming = graph.incomingEdgesOf(next.getHasAsOrigin()); for (LimitingResourceQueueDependency each : incoming) { if ( each.propagatesThrough(next) ) { if ( each.isOriginNotDetached() ) { result.add(each); } else { fillIncoming(each, result); } } } } /** * @return all the gaps that could potentially fit <code>element</code> * ordered by start date */ public List<GapOnQueue> getPotentiallyValidGapsFor(InsertionRequirements requirements) { List<LimitingResourceQueue> assignableQueues = getAssignableQueues(requirements.getElement()); List<List<GapOnQueue>> allGaps = gapsFor(assignableQueues, requirements); return GapsMergeSort.sort(allGaps); } private List<List<GapOnQueue>> gapsFor(List<LimitingResourceQueue> assignableQueues, InsertionRequirements requirements) { List<List<GapOnQueue>> result = new ArrayList<>(); for (LimitingResourceQueue each : assignableQueues) { result.add(each.getGapsPotentiallyValidFor(requirements)); } return result; } private List<LimitingResourceQueue> findQueuesMatchingCriteria(GenericResourceAllocation generic) { List<LimitingResourceQueue> result = new ArrayList<>(); ResourceEnum resourceType = generic.getResourceType(); Set<Criterion> criteria = generic.getCriterions(); for (LimitingResourceQueue each : queues) { Resource resource = each.getResource(); if ( resource.getType().equals(resourceType) && resource.satisfiesCriterionsAtSomePoint(criteria) ) { result.add(each); } } return result; } public static class Edge { public final LimitingResourceQueueElement source; public final LimitingResourceQueueElement target; public final QueueDependencyType type; public static Edge from(LimitingResourceQueueDependency dependency) { return new Edge(dependency.getHasAsOrigin(), dependency.getHasAsDestiny(), dependency.getType()); } public static Edge insertionOrder(LimitingResourceQueueElement element, LimitingResourceQueueElement contiguousNext) { return new Edge(element, contiguousNext, QueueDependencyType.END_START); } private Edge(LimitingResourceQueueElement source, LimitingResourceQueueElement target, QueueDependencyType type) { Validate.notNull(source); Validate.notNull(target); Validate.notNull(type); this.source = source; this.target = target; this.type = type; } @Override public int hashCode() { return new HashCodeBuilder().append(source).append(target).append(type).toHashCode(); } @Override public boolean equals(Object obj) { if ( obj instanceof Edge ) { Edge another = (Edge) obj; return new EqualsBuilder() .append(source, another.source) .append(target, another.target) .append(type, another.type).isEquals(); } return false; } } public DirectedGraph<LimitingResourceQueueElement, Edge> getPotentiallyAffectedByInsertion( LimitingResourceQueueElement element) { DirectedMultigraph<LimitingResourceQueueElement, Edge> result; result = asEdges(onQueues(buildOutgoingGraphFor(getEquivalent(element)))); Map<LimitingResourceQueue, LimitingResourceQueueElement> earliestForEachQueue = earliest(byQueue(result.vertexSet())); for (Entry<LimitingResourceQueue, LimitingResourceQueueElement> each : earliestForEachQueue.entrySet()) { LimitingResourceQueue queue = each.getKey(); LimitingResourceQueueElement earliest = each.getValue(); addInsertionOrderOnQueueEdges(result, earliest, queue.getElementsAfter(earliest)); } return result; } private DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> onQueues( DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> graph) { SimpleDirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> result; result = instantiateDirectedGraph(); for (LimitingResourceQueueDependency each : graph.edgeSet()) { if ( !each.getHasAsOrigin().isDetached() && !each.getHasAsDestiny().isDetached() ) { addDependency(result, each); } } return result; } private DirectedMultigraph<LimitingResourceQueueElement, Edge> asEdges( DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> graph) { DirectedMultigraph<LimitingResourceQueueElement, Edge> result = instantiateMultiGraph(); for (LimitingResourceQueueDependency each : graph.edgeSet()) { Edge edge = Edge.from(each); result.addVertex(edge.source); result.addVertex(edge.target); result.addEdge(edge.source, edge.target, edge); } return result; } private DirectedMultigraph<LimitingResourceQueueElement, Edge> instantiateMultiGraph() { return new DirectedMultigraph<>(Edge.class); } private Map<LimitingResourceQueue, List<LimitingResourceQueueElement>> byQueue( Collection<? extends LimitingResourceQueueElement> vertexSet) { Map<LimitingResourceQueue, List<LimitingResourceQueueElement>> result = new HashMap<>(); for (LimitingResourceQueueElement each : vertexSet) { assert each.getLimitingResourceQueue() != null; forQueue(result, each.getLimitingResourceQueue()).add(each); } return result; } private List<LimitingResourceQueueElement> forQueue( Map<LimitingResourceQueue, List<LimitingResourceQueueElement>> map, LimitingResourceQueue queue) { List<LimitingResourceQueueElement> result = map.get(queue); if ( result == null ) { result = new ArrayList<>(); map.put(queue, result); } return result; } private static Map<LimitingResourceQueue, LimitingResourceQueueElement> earliest( Map<LimitingResourceQueue, List<LimitingResourceQueueElement>> byQueue) { Map<LimitingResourceQueue, LimitingResourceQueueElement> result = new HashMap<>(); for (Entry<LimitingResourceQueue, List<LimitingResourceQueueElement>> each : byQueue.entrySet()) { result.put(each.getKey(), earliest(each.getValue())); } return result; } private static LimitingResourceQueueElement earliest(List<LimitingResourceQueueElement> list) { Validate.isTrue(!list.isEmpty()); return Collections.min(list, LimitingResourceQueueElement.byStartTimeComparator()); } private void addInsertionOrderOnQueueEdges( DirectedGraph<LimitingResourceQueueElement, Edge> result, LimitingResourceQueueElement first, List<LimitingResourceQueueElement> elements) { LimitingResourceQueueElement previous = first; for (LimitingResourceQueueElement each : elements) { // Fixes bug #553, "No such vertex in graph". // It seems that, for some reason, some of the vertexes (queue elements) are not in graph at this point if ( !result.containsVertex(previous) ) { result.addVertex(previous); } if ( !result.containsVertex(each) ) { result.addVertex(each); } result.addEdge(previous, each, Edge.insertionOrder(previous, each)); previous = each; } } /** * @param externalQueueElement * the queue element to insert * @return the list of elements that must be reinserted due to the insertion * of <code>externalQueueElement</code> */ public List<LimitingResourceQueueElement> getInsertionsToBeDoneFor( LimitingResourceQueueElement externalQueueElement) { LimitingResourceQueueElement queueElement = getEquivalent(externalQueueElement); DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> subGraph = buildOutgoingGraphFor(queueElement); CycleDetector<LimitingResourceQueueElement, LimitingResourceQueueDependency> cycleDetector = cycleDetector(subGraph); if ( cycleDetector.detectCycles() ) { throw new IllegalStateException("subgraph has cycles"); } List<LimitingResourceQueueElement> result = new ArrayList<>(); result.add(queueElement); result.addAll(getElementsOrderedTopologically(subGraph)); unassignFromQueues(result); return result; } private DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> buildOutgoingGraphFor( LimitingResourceQueueElement queueElement) { SimpleDirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> result = instantiateDirectedGraph(); buildOutgoingGraphFor(result, queueElement); return result; } private void buildOutgoingGraphFor( DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> result, LimitingResourceQueueElement element) { Set<LimitingResourceQueueDependency> outgoingEdgesOf = graph.outgoingEdgesOf(element); result.addVertex(element); for (LimitingResourceQueueDependency each : outgoingEdgesOf) { addDependency(result, each); buildOutgoingGraphFor(result, each.getHasAsDestiny()); } } private CycleDetector<LimitingResourceQueueElement, LimitingResourceQueueDependency> cycleDetector( DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> subGraph) { return new CycleDetector<>(subGraph); } private List<LimitingResourceQueueElement> getElementsOrderedTopologically( DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> subGraph) { return onlyAssigned(toList(topologicalIterator(subGraph))); } public static <V, E> TopologicalOrderIterator<V, E> topologicalIterator(DirectedGraph<V, E> subGraph) { return new TopologicalOrderIterator<>(subGraph); } public static <T> List<T> toList(final Iterator<T> iterator) { List<T> result = new ArrayList<T>(); while (iterator.hasNext()) { result.add(iterator.next()); } return result; } private List<LimitingResourceQueueElement> onlyAssigned(List<LimitingResourceQueueElement> list) { List<LimitingResourceQueueElement> result = new ArrayList<>(); for (LimitingResourceQueueElement each : list) { if ( !each.isDetached() ) { result.add(each); } } return result; } private void unassignFromQueues(List<LimitingResourceQueueElement> result) { for (LimitingResourceQueueElement each : result) { if ( !each.isDetached() ) { unassingFromQueue(each); } } } public void replaceLimitingResourceQueueElement( LimitingResourceQueueElement oldElement, LimitingResourceQueueElement newElement) { if ( oldElement.isNewObject() ) { elementsById.put(oldElement.getId(), oldElement); } LimitingResourceQueueElement element = getEquivalent(oldElement); if ( element.hasDayAssignments() ) { unassingFromQueue(element); } unassignedElements.remove(element); elementsById.remove(element.getId()); graph.removeVertex(element); unassignedElements.add(newElement); elementsById.put(newElement.getId(), newElement); graph.addVertex(newElement); for (LimitingResourceQueueDependency each: newElement.getDependenciesAsOrigin()) { graph.addEdge(each.getHasAsOrigin(), each.getHasAsDestiny(), each); } for (LimitingResourceQueueDependency each: newElement.getDependenciesAsDestiny()) { graph.addEdge(each.getHasAsOrigin(), each.getHasAsDestiny(), each); } } public void idChangedFor(Long previousId, LimitingResourceQueueElement element) { elementsById.remove(previousId); elementsById.put(element.getId(), element); } public List<LimitingResourceQueueElement> inTopologicalOrder(List<LimitingResourceQueueElement> queueElements) { return toList(topologicalIterator(buildSubgraphFor(queueElements))); } /** * Constructs a graph composed only by queueElements * * @param queueElements * @return */ private DirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> buildSubgraphFor( List<LimitingResourceQueueElement> queueElements) { SimpleDirectedGraph<LimitingResourceQueueElement, LimitingResourceQueueDependency> result = instantiateDirectedGraph(); // Iterate through elements and construct graph for (LimitingResourceQueueElement each : queueElements) { result.addVertex(each); for (LimitingResourceQueueDependency dependency : each.getDependenciesAsOrigin()) { LimitingResourceQueueElement destiny = dependency.getHasAsDestiny(); if ( queueElements.contains(destiny) ) { // Add source, destiny and edge between them addDependency(result, dependency); } } } return result; } /** * Returns a list of gaps plus its right-adjacent queueElement starting from * allocationTime * * If there's already an element at allocationTime in the queue, the gap is * null and the element is the element at that position * * @param queue * @param allocationTime * @return */ public List<GapOnQueueWithQueueElement> getGapsWithQueueElementsOnQueueSince( LimitingResourceQueue queue, DateAndHour allocationTime) { return gapsWithQueueElementsFor(queue, allocationTime); } private List<GapOnQueueWithQueueElement> gapsWithQueueElementsFor( LimitingResourceQueue queue, DateAndHour allocationTime) { List<GapOnQueueWithQueueElement> result = new ArrayList<>(); DateAndHour previousEnd = null; for (LimitingResourceQueueElement each : queue.getLimitingResourceQueueElements()) { DateAndHour startTime = each.getStartTime(); if ( !startTime.isBefore(allocationTime) ) { if ( previousEnd == null || startTime.isAfter(previousEnd) ) { Gap gap = Gap.create(queue.getResource(), previousEnd, startTime); result.add(GapOnQueueWithQueueElement.create(gap.onQueue(queue), each)); } } previousEnd = each.getEndTime(); } Gap gap = Gap.create(queue.getResource(), previousEnd, null); result.add(GapOnQueueWithQueueElement.create(gap.onQueue(queue), null)); return result; } }