/*
* 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.business.planner.entities;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.Validate;
import javax.validation.Valid;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
import org.libreplan.business.calendars.entities.Capacity;
import org.libreplan.business.calendars.entities.ICalendar;
import org.libreplan.business.planner.entities.AssignedEffortForResource.IAssignedEffortForResource;
import org.libreplan.business.planner.entities.EffortDistributor.IResourceSelector;
import org.libreplan.business.planner.entities.EffortDistributor.ResourceWithAssignedDuration;
import org.libreplan.business.planner.entities.allocationalgorithms.ResourcesPerDayModification;
import org.libreplan.business.resources.daos.IResourcesSearcher;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.CriterionCompounder;
import org.libreplan.business.resources.entities.ICriterion;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.ResourceEnum;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.util.deepcopy.OnCopy;
import org.libreplan.business.util.deepcopy.Strategy;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
import org.libreplan.business.workingday.ResourcesPerDay;
/**
* Represents the relation between {@link Task} and a generic {@link Resource}.
*
* @author Diego Pino García <dpino@igalia.com>
*/
public class GenericResourceAllocation extends ResourceAllocation<GenericDayAssignment> {
@OnCopy(Strategy.SHARE_COLLECTION_ELEMENTS)
private Set<Criterion> criterions = new HashSet<>();
@OnCopy(Strategy.SHARE)
private ResourceEnum resourceType;
private Set<GenericDayAssignmentsContainer> genericDayAssignmentsContainers = new HashSet<>();
private IAssignedEffortForResource assignedEffortForResource = null;
/**
* Constructor for Hibernate. DO NOT USE!
*/
public GenericResourceAllocation() {
}
public static GenericResourceAllocation create() {
return create(new GenericResourceAllocation());
}
public static GenericResourceAllocation createForTesting(ResourcesPerDay resourcesPerDay, Task task) {
return create(new GenericResourceAllocation(resourcesPerDay, task));
}
public static Map<Set<Criterion>, List<GenericResourceAllocation>> byCriterions(
Collection<GenericResourceAllocation> genericAllocations) {
Map<Set<Criterion>, List<GenericResourceAllocation>> result = new HashMap<>();
for (GenericResourceAllocation genericResourceAllocation : genericAllocations) {
Set<Criterion> criterions = genericResourceAllocation.getCriterions();
if ( !result.containsKey(criterions) ) {
result.put(criterions, new ArrayList<>());
}
result.get(criterions).add(genericResourceAllocation);
}
return result;
}
@Valid
@SuppressWarnings("unused")
private Set<GenericDayAssignmentsContainer> getGenericDayAssignmentsContainers() {
return new HashSet<>(genericDayAssignmentsContainers);
}
private GenericResourceAllocation(ResourcesPerDay resourcesPerDay, Task task) {
super(resourcesPerDay, task);
}
public static GenericResourceAllocation create(Task task) {
return create(new GenericResourceAllocation(task));
}
public static GenericResourceAllocation create(Task task, Collection<? extends Criterion> criterions) {
return create(task, inferType(criterions), criterions);
}
public static ResourceEnum inferType(Collection<? extends Criterion> criterions) {
return criterions.isEmpty()
? ResourceEnum.WORKER
: criterions.iterator().next().getType().getResource();
}
public static GenericResourceAllocation create(
Task task, ResourceEnum resourceType, Collection<? extends Criterion> criterions) {
Validate.notNull(resourceType);
GenericResourceAllocation result = new GenericResourceAllocation(task);
result.criterions = new HashSet<>(criterions);
result.resourceType = resourceType;
result.setResourcesPerDayToAmount(1);
return create(result);
}
private GenericResourceAllocation(Task task) {
super(task);
this.criterions = task.getCriterions();
}
@Override
protected GenericDayAssignmentsContainer retrieveOrCreateContainerFor(Scenario scenario) {
GenericDayAssignmentsContainer retrieved = retrieveContainerFor(scenario);
if (retrieved != null) {
return retrieved;
}
GenericDayAssignmentsContainer result = GenericDayAssignmentsContainer.create(this, scenario);
genericDayAssignmentsContainers.add(result);
return result;
}
@Override
protected GenericDayAssignmentsContainer retrieveContainerFor(Scenario scenario) {
return containersByScenario().get(scenario);
}
private Map<Scenario, GenericDayAssignmentsContainer> containersByScenario() {
Map<Scenario, GenericDayAssignmentsContainer> result = new HashMap<>();
for (GenericDayAssignmentsContainer each : genericDayAssignmentsContainers) {
assert !result.containsKey(each);
result.put(each.getScenario(), each);
}
return result;
}
public List<GenericDayAssignment> getOrderedAssignmentsFor(Resource resource) {
List<GenericDayAssignment> assignments = getOrderedAssignmentsFor().get(resource);
return assignments == null ? Collections.emptyList() : Collections.unmodifiableList(assignments);
}
private Map<Resource, List<GenericDayAssignment>> getOrderedAssignmentsFor() {
return DayAssignment.byResourceAndOrdered(getDayAssignmentsState().getUnorderedAssignments());
}
public Set<Criterion> getCriterions() {
return Collections.unmodifiableSet(criterions);
}
private final class ResourcesSatisfyingCriterionsSelector implements IResourceSelector {
@Override
public boolean isSelectable(Resource resource, LocalDate day) {
ICriterion compoundCriterion = CriterionCompounder.buildAnd(criterions).getResult();
return compoundCriterion.isSatisfiedBy(resource, day);
}
}
private class GenericAllocation extends AssignmentsAllocator {
private EffortDistributor hoursDistributor;
private final List<Resource> resources;
public GenericAllocation(List<Resource> resources) {
this.resources = resources;
hoursDistributor = new EffortDistributor(
resources, getAssignedEffortForResource(), new ResourcesSatisfyingCriterionsSelector());
}
@Override
public List<GenericDayAssignment> distributeForDay(PartialDay day, EffortDuration effort) {
List<GenericDayAssignment> result = new ArrayList<>();
for (ResourceWithAssignedDuration each : hoursDistributor.distributeForDay(day, effort)) {
result.add(GenericDayAssignment.create(day.getDate(), each.duration, each.resource));
}
return result;
}
@Override
protected AvailabilityTimeLine getResourcesAvailability() {
return AvailabilityCalculator.buildSumOfAvailabilitiesFor(getCriterions(), resources);
}
@Override
protected Capacity getCapacityAt(PartialDay day) {
return hoursDistributor.getCapacityAt(day);
}
}
public void setAssignedEffortForResource(IAssignedEffortForResource assignedEffortForResource) {
this.assignedEffortForResource = assignedEffortForResource;
}
private IAssignedEffortForResource getAssignedEffortForResource() {
return assignedEffortForResource != null
? assignedEffortForResource
: AssignedEffortForResource.effortDiscounting(Collections.singletonList(this));
}
@Override
protected ICalendar getCalendarGivenTaskCalendar(ICalendar taskCalendar) {
return taskCalendar;
}
public IAllocatable forResources(Collection<? extends Resource> resources) {
return new GenericAllocation(new ArrayList<>(resources));
}
@Override
protected void setItselfAsParentFor(GenericDayAssignment dayAssignment) {
dayAssignment.setGenericResourceAllocation(this);
}
@Override
protected Class<GenericDayAssignment> getDayAssignmentType() {
return GenericDayAssignment.class;
}
@Override
public List<Resource> getAssociatedResources() {
return new ArrayList<>(DayAssignment.getAllResources(getAssignments()));
}
@Override
public IAllocatable withPreviousAssociatedResources() {
return forResources(getAssociatedResources());
}
@Override
ResourceAllocation<GenericDayAssignment> createCopy(Scenario scenario) {
GenericResourceAllocation allocation = create();
allocation.criterions = new HashSet<>(criterions);
return allocation;
}
@Override
public ResourcesPerDayModification withDesiredResourcesPerDay(ResourcesPerDay resourcesPerDay) {
return ResourcesPerDayModification.create(this, resourcesPerDay, getAssociatedResources());
}
@Override
public List<Resource> querySuitableResources(IResourcesSearcher resourcesSearcher) {
return resourcesSearcher.searchBoth().byCriteria(getCriterions()).execute();
}
public static Map<Criterion, List<GenericResourceAllocation>> byCriterion(
List<GenericResourceAllocation> generics) {
Map<Criterion, List<GenericResourceAllocation>> result = new HashMap<>();
for (GenericResourceAllocation genericResourceAllocation : generics) {
Set<Criterion> criterions = genericResourceAllocation.getCriterions();
for (Criterion criterion : criterions) {
if (!result.containsKey(criterion)) {
result.put(criterion, new ArrayList<>());
}
result.get(criterion).add(genericResourceAllocation);
}
}
return result;
}
@Override
public void makeAssignmentsContainersDontPoseAsTransientAnyMore() {
for (GenericDayAssignmentsContainer each : genericDayAssignmentsContainers) {
each.dontPoseAsTransientObjectAnymore();
}
}
@Override
public void copyAssignments(Scenario from, Scenario to) {
GenericDayAssignmentsContainer fromContainer = retrieveOrCreateContainerFor(from);
GenericDayAssignmentsContainer toContainer = retrieveOrCreateContainerFor(to);
toContainer.resetTo(fromContainer.getDayAssignments());
}
@Override
protected void removePredecessorContainersFor(Scenario scenario) {
Map<Scenario, GenericDayAssignmentsContainer> byScenario = containersByScenario();
for (Scenario each : scenario.getPredecessors()) {
GenericDayAssignmentsContainer container = byScenario.get(each);
if (container != null) {
genericDayAssignmentsContainers.remove(container);
}
}
}
@Override
protected void removeContainersFor(Scenario scenario) {
GenericDayAssignmentsContainer container = containersByScenario().get(scenario);
if (container != null) {
genericDayAssignmentsContainers.remove(container);
}
}
public void overrideConsolidatedDayAssignments(GenericResourceAllocation origin) {
if (origin != null) {
List<GenericDayAssignment> originAssignments = origin.getConsolidatedAssignments();
resetAssignmentsTo(GenericDayAssignment.copyToAssignmentsWithoutParent(originAssignments));
}
}
public ResourceEnum getResourceType() {
return resourceType != null ? resourceType : inferType(criterions);
}
@Override
public EffortDuration getAssignedEffort(
Criterion criterion, IntraDayDate startInclusive, IntraDayDate endExclusive) {
return super.getAssignedDuration(startInclusive, endExclusive);
}
public IEffortDistributor<GenericDayAssignment> createEffortDistributor(List<Resource> resources) {
return new GenericAllocation(resources);
}
}