/*
* 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.resources.entities;
import static org.libreplan.business.common.exceptions.ValidationException.invalidValue;
import static org.libreplan.business.workingday.EffortDuration.zero;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import javax.validation.Valid;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.calendars.entities.ICalendar;
import org.libreplan.business.calendars.entities.ResourceCalendar;
import org.libreplan.business.calendars.entities.SameWorkHoursEveryDay;
import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.common.IHumanIdentifiable;
import org.libreplan.business.common.IOnTransaction;
import org.libreplan.business.common.IntegrationEntity;
import org.libreplan.business.common.Registry;
import org.libreplan.business.common.entities.Configuration;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.MultipleInstancesException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.costcategories.entities.CostCategory;
import org.libreplan.business.costcategories.entities.ResourcesCostCategoryAssignment;
import org.libreplan.business.planner.entities.AvailabilityCalculator;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.resources.daos.IResourceDAO;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.EffortDuration.IEffortFrom;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
/**
* This class acts as the base class for all resources.
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
* @author Susana Montes Pedreira <smontes@wirelessgalicia.com>
* @author Jacobo Aragunde Perez <jaragunde@igalia.com>
*/
public abstract class Resource extends IntegrationEntity implements IHumanIdentifiable, Comparable<Resource> {
public static class AllResourceAssignments implements IAssignmentsOnResourceCalculator {
@Override
public List<DayAssignment> getAssignments(Resource resource) {
return resource.getAssignments();
}
}
public static List<Machine> machines(Collection<? extends Resource> resources) {
return filter(Machine.class, resources);
}
public static List<Worker> workers(Collection<? extends Resource> resources) {
return filter(Worker.class, resources);
}
public static <T extends Resource> List<T> filter(Class<T> klass, Collection<? extends Resource> resources) {
List<T> result = new ArrayList<T>();
for (Resource each : resources) {
if ( klass.isInstance(each) ) {
result.add(klass.cast(each));
}
}
return result;
}
public static List<Resource> sortByName(List<Resource> resources) {
Collections.sort(resources, new Comparator<Resource>() {
@Override
public int compare(Resource o1, Resource o2) {
return String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName());
}
});
return resources;
}
public static String getCaptionFor(ResourceAllocation<?> resourceAllocation) {
return getCaptionFor(resourceAllocation.getAssociatedResources());
}
public static String getCaptionFor(List<Resource> resources) {
List<String> values = new ArrayList<String>();
for (Resource each: resources) {
values.add(each.getShortDescription());
}
return StringUtils.join(values, ", ");
}
private ResourceCalendar calendar;
private Set<CriterionSatisfaction> criterionSatisfactions = new HashSet<CriterionSatisfaction>();
private Set<DayAssignment> dayAssignments = new HashSet<DayAssignment>();
private Map<LocalDate, List<DayAssignment>> assignmentsByDayCached = null;
private Set<ResourcesCostCategoryAssignment> resourcesCostCategoryAssignments =
new HashSet<ResourcesCostCategoryAssignment>();
private ResourceType resourceType = ResourceType.NON_LIMITING_RESOURCE;
private LimitingResourceQueue limitingResourceQueue;
private void clearCachedData() {
assignmentsByDayCached = null;
dayAssignmentsState.clearCachedData();
}
private List<DayAssignment> getAssignmentsForDay(LocalDate date) {
if ( assignmentsByDayCached == null ) {
assignmentsByDayCached = DayAssignment.byDay(getAssignments());
}
List<DayAssignment> list = assignmentsByDayCached.get(date);
if ( list == null ){
return Collections.emptyList();
}
return list;
}
private abstract class DayAssignmentsState {
private List<DayAssignment> cachedAssignments;
abstract List<DayAssignment> calculateAssignments();
List<DayAssignment> getAssignments() {
if ( cachedAssignments != null ) {
return cachedAssignments;
}
return cachedAssignments = calculateAssignments();
}
void clearCachedData() {
cachedAssignments = null;
}
}
private class UsingScenarioManager extends DayAssignmentsState {
@Override
List<DayAssignment> calculateAssignments() {
List<DayAssignment> result = new ArrayList<DayAssignment>();
Scenario current = Registry.getScenarioManager().getCurrent();
for (DayAssignment each : dayAssignments) {
if ( each.getScenario() != null && each.getScenario().equals(current) ) {
result.add(each);
}
}
return result;
}
}
private class OnSpecifiedScenario extends DayAssignmentsState {
private final Scenario currentScenario;
private OnSpecifiedScenario(Scenario currentScenario) {
Validate.notNull(currentScenario);
this.currentScenario = currentScenario;
}
@Override
List<DayAssignment> calculateAssignments() {
List<DayAssignment> result = new ArrayList<DayAssignment>();
for (DayAssignment each : dayAssignments) {
if ( isTransient(each) || each.getScenario().equals(currentScenario) ) {
result.add(each);
}
}
return result;
}
private boolean isTransient(DayAssignment each) {
return each.getScenario() == null;
}
}
private DayAssignmentsState dayAssignmentsState = new UsingScenarioManager();
@Valid
public Set<CriterionSatisfaction> getCriterionSatisfactions() {
Set<CriterionSatisfaction> satisfactionActives = new HashSet<CriterionSatisfaction>();
for(CriterionSatisfaction satisfaction:criterionSatisfactions){
if( !satisfaction.isIsDeleted() ) {
satisfactionActives.add(satisfaction);
}
}
return satisfactionActives;
}
public CriterionSatisfaction getCriterionSatisfactionByCode(String code) throws InstanceNotFoundException {
if ( StringUtils.isBlank(code) ) {
throw new InstanceNotFoundException(code, CriterionSatisfaction.class.getName());
}
for (CriterionSatisfaction i : criterionSatisfactions) {
if ( i.getCode().equalsIgnoreCase(StringUtils.trim(code)) ) {
return i;
}
}
throw new InstanceNotFoundException(code, CriterionSatisfaction.class.getName());
}
public abstract String getShortDescription();
public abstract String getName();
private interface IPredicate {
public boolean accepts(CriterionSatisfaction satisfaction);
}
public class Query {
private List<IPredicate> predicates = new ArrayList<IPredicate>();
private Query() {
}
public Query from(final ICriterionType<?> type) {
return withNewPredicate(new IPredicate() {
@Override
public boolean accepts(CriterionSatisfaction satisfaction) {
return type.contains(satisfaction.getCriterion());
}
});
}
private Query withNewPredicate(IPredicate newPredicate) {
predicates.add(newPredicate);
return this;
}
public Query at(LocalDate date) {
return enforcedInAll(Interval.point(date));
}
public Query between(LocalDate start, LocalDate end) {
return enforcedInAll(Interval.range(start, end));
}
public Query enforcedInAll(final Interval interval) {
return withNewPredicate(new IPredicate() {
@Override
public boolean accepts(CriterionSatisfaction satisfaction) {
return satisfaction.isAlwaysEnforcedIn(interval);
}
});
}
public Query overlapsWith(final Interval interval) {
return withNewPredicate(new IPredicate() {
@Override
public boolean accepts(CriterionSatisfaction satisfaction) {
return satisfaction.overlapsWith(interval);
}
});
}
public Query from(final Criterion criterion) {
return withNewPredicate(new IPredicate() {
@Override
public boolean accepts(CriterionSatisfaction satisfaction) {
return criterion.includes(satisfaction.getCriterion());
}
});
}
public Query exactly(final Criterion criterion) {
return withNewPredicate(new IPredicate() {
@Override
public boolean accepts(CriterionSatisfaction satisfaction) {
return criterion.isEquivalent(satisfaction.getCriterion());
}
});
}
/**
* Method called to retrieve the result. If no predicate was set, it
* returns all satisfactions
* @return the satisfactions matched by all predicates specified ordered
* by start date.
*/
public List<CriterionSatisfaction> result() {
ArrayList<CriterionSatisfaction> result = new ArrayList<CriterionSatisfaction>();
for (CriterionSatisfaction criterionSatisfaction : getCriterionSatisfactions()) {
if (isAcceptedByAllPredicates(criterionSatisfaction)) {
result.add(criterionSatisfaction);
}
}
Collections.sort(result, CriterionSatisfaction.BY_START_COMPARATOR);
return result;
}
public List<CriterionSatisfaction> result(Set<CriterionSatisfaction> list) {
ArrayList<CriterionSatisfaction> result = new ArrayList<CriterionSatisfaction>();
for (CriterionSatisfaction criterionSatisfaction : list) {
if (isAcceptedByAllPredicates(criterionSatisfaction)) {
result.add(criterionSatisfaction);
}
}
Collections.sort(result, CriterionSatisfaction.BY_START_COMPARATOR);
return result;
}
private boolean isAcceptedByAllPredicates(
CriterionSatisfaction criterionSatisfaction) {
for (IPredicate predicate : predicates) {
if (!predicate.accepts(criterionSatisfaction)) {
return false;
}
}
return true;
}
public Query current() {
return withNewPredicate(new IPredicate() {
@Override
public boolean accepts(CriterionSatisfaction satisfaction) {
return satisfaction.isCurrent();
}
});
}
public List<Criterion> asCriterions() {
LinkedHashSet<Criterion> result = new LinkedHashSet<Criterion>();
for (CriterionSatisfaction criterionSatisfaction : result()) {
result.add(criterionSatisfaction.getCriterion());
}
return new ArrayList<Criterion>(result);
}
public Query oneOf(ICriterionType<?>[] laboralRelatedTypes) {
return oneOf(Arrays.asList(laboralRelatedTypes));
}
public Query oneOf(final Collection<? extends ICriterionType<?>> types) {
return withNewPredicate(new IPredicate() {
@Override
public boolean accepts(CriterionSatisfaction satisfaction) {
for (ICriterionType<?> criterionType : types) {
if (criterionType.contains(satisfaction.getCriterion())) {
return true;
}
}
return false;
}
});
}
}
public Query query() {
return new Query();
}
public Set<CriterionSatisfaction> getAllSatisfactions() {
return new HashSet<CriterionSatisfaction>(criterionSatisfactions);
}
public Collection<CriterionSatisfaction> getSatisfactionsFor(
ICriterionType<?> type) {
return query().from(type).result();
}
public List<CriterionSatisfaction> getSatisfactionsFor(Criterion criterion) {
return query().from(criterion).result();
}
public List<Criterion> getCurrentCriterionsFor(ICriterionType<?> type) {
return query().from(type).current().asCriterions();
}
public Collection<CriterionSatisfaction> getCurrentSatisfactionsFor(
ICriterionType<?> criterionType) {
return query().from(criterionType).current().result();
}
public List<CriterionSatisfaction> getCurrentSatisfactionsFor(
Criterion criterion) {
return query().from(criterion).current().result();
}
public CriterionSatisfaction addSatisfaction(
CriterionWithItsType criterionWithItsType) {
LocalDate today = new LocalDate();
return addSatisfaction(criterionWithItsType, Interval.from(today));
}
private static class EnsureSatisfactionIsCorrect {
private EnsureSatisfactionIsCorrect(Resource resource,
ICriterionType<?> type, CriterionSatisfaction satisfaction) {
Validate.notNull(resource);
Validate.notNull(satisfaction.getResource());
Validate.notNull(satisfaction);
if (!satisfaction.getResource().equals(resource)) {
throw new IllegalArgumentException(
"the satisfaction is not related to this resource");
}
this.type = new CriterionWithItsType(type, satisfaction
.getCriterion());
this.interval = satisfaction.getInterval();
this.resource = resource;
}
private final Resource resource;
private final CriterionWithItsType type;
private final Interval interval;
CriterionSatisfaction addSatisfaction() {
return resource.addSatisfaction(type, interval);
}
boolean canAddSatisfaction() {
return resource.canAddSatisfaction(type, interval);
}
}
public CriterionSatisfaction addSatisfaction(ICriterionType<?> type,
CriterionSatisfaction satisfaction) {
return new EnsureSatisfactionIsCorrect(this, type, satisfaction)
.addSatisfaction();
}
public CriterionSatisfaction addSatisfaction(
CriterionWithItsType criterionWithItsType, Interval interval){
Criterion criterion = criterionWithItsType.getCriterion();
ICriterionType<?> type = criterionWithItsType.getType();
CriterionSatisfaction newSatisfaction = createNewSatisfaction(interval,
criterion);
if (canAddSatisfaction(criterionWithItsType, interval)) {
newSatisfaction.validate();
criterionSatisfactions.add(newSatisfaction);
return newSatisfaction;
}
final String message = getReasonForNotAddingSatisfaction(type);
throw new IllegalStateException(message);
}
private String getReasonForNotAddingSatisfaction(ICriterionType<?> type) {
if (cannotApplyResourceToCriterionType(type)) {
return "Cannot apply criterion of type " + type.getName()
+ " to a " + getClass().getSimpleName();
} else {
return "Criterion satisfaction overlaps with other criterion satisfactions";
}
}
private boolean cannotApplyResourceToCriterionType(ICriterionType<?> type) {
return (type != null && !type.criterionCanBeRelatedTo(getClass()));
}
private CriterionSatisfaction createNewSatisfaction(Interval interval,
Criterion criterion) {
CriterionSatisfaction newSatisfaction = CriterionSatisfaction.create(criterion, this, interval);
return newSatisfaction;
}
/**
* @param orderedSatisfactions
* @param newSatisfaction
* @return the position in which if newSatisfaction is inserted would comply
* with the following:
* <ul>
* <li>newSatisfaction startDate would be equal or posterior to all
* the previous satisfactions</li>
* <li>newSatisfaction startDate would be previous to all the
* posterior satisfactions</li>
* </ul>
*/
private int findPlace(List<CriterionSatisfaction> orderedSatisfactions,
CriterionSatisfaction newSatisfaction) {
int position = Collections.binarySearch(orderedSatisfactions,
newSatisfaction, CriterionSatisfaction.BY_START_COMPARATOR);
if (position >= 0) {
return position + 1;
} else {
return Math.abs(position) - 1;
}
}
public List<CriterionSatisfaction> finish(
CriterionWithItsType criterionWithItsType) {
LocalDate today = new LocalDate();
return finishEnforcedAt(criterionWithItsType.getCriterion(), today);
}
public List<CriterionSatisfaction> finishEnforcedAt(Criterion criterion,
LocalDate date) {
ArrayList<CriterionSatisfaction> result = new ArrayList<CriterionSatisfaction>();
for (CriterionSatisfaction criterionSatisfaction : query()
.exactly(criterion).at(date).result()) {
criterionSatisfaction.finish(date);
result.add(criterionSatisfaction);
}
return result;
}
public void modifySatisfaction(CriterionSatisfaction original,
Interval interval){
/* Create a temporal criterion satisfaction. */
CriterionType type = original.getCriterion().getType();
CriterionSatisfaction temporal = createNewSatisfaction(interval,
original.getCriterion());
temporal.setResource(this);
boolean canAdd=false;
if (contains(original)) {
try{
removeCriterionSatisfaction(original);
canAdd = canAddSatisfaction(type, temporal);
if(canAdd){
//update original
original.setStartDate(interval.getStart());
original.finish(interval.getEnd());
}
original.validate();
criterionSatisfactions.add(original);
if(!canAdd){
throw new IllegalStateException(
"This interval "+original.getCriterion().getName()+" not is valid because exists overlap with other criterion satisfaction");
}
}catch(IllegalArgumentException e){
throw new IllegalArgumentException (original.getCriterion().getName()+" : "+e.getMessage());
}
}else{
throw new IllegalStateException(
"The criterion satisfaction "+original.getCriterion().getName()+" not is activated for this resource");
}
}
public boolean canAddSatisfaction(
CriterionWithItsType criterionWithItsType, Interval interval) {
CriterionSatisfaction satisfaction = createNewSatisfaction(interval, criterionWithItsType.getCriterion());
return canAddSatisfaction(criterionWithItsType.getType(), satisfaction, this.getCriterionSatisfactions());
}
private boolean canAddSatisfaction(ICriterionType<?> type, CriterionSatisfaction satisfaction, Set<CriterionSatisfaction> satisfactions) {
final Criterion criterion = satisfaction.getCriterion();
final Interval interval = Interval.range(satisfaction.getStartDate(), satisfaction.getEndDate());
if (!type.criterionCanBeRelatedTo(getClass())) {
return false;
}
CriterionSatisfaction previousSameCriterion = getPreviousSameCriterion
(criterion, satisfaction, satisfactions);
CriterionSatisfaction posteriorSameCriterion = getNextSameCriterion
(criterion, satisfaction, satisfactions);
boolean canAdd = ((previousSameCriterion == null ||
!previousSameCriterion.overlapsWith(interval)) &&
( posteriorSameCriterion == null ||
!posteriorSameCriterion.overlapsWith(interval)));
if(!canAdd) {
return false;
}
if (type.isAllowSimultaneousCriterionsPerResource()){
return true;
}
CriterionSatisfaction previous = getPrevious(type , satisfaction, satisfactions);
CriterionSatisfaction posterior = getNext(type, satisfaction, satisfactions);
return (previous == null || !previous.overlapsWith(interval)) &&
( posterior == null || !posterior.overlapsWith(interval));
}
public boolean _canAddSatisfaction(
CriterionWithItsType criterionWithItsType, Interval interval) {
ICriterionType<?> type = criterionWithItsType.getType();
Criterion criterion = criterionWithItsType.getCriterion();
if (!type.criterionCanBeRelatedTo(getClass())) {
return false;
}
CriterionSatisfaction newSatisfaction = createNewSatisfaction(interval,
criterion);
CriterionSatisfaction previousSameCriterion = getPreviousSameCriterion
(criterion, newSatisfaction,this.getCriterionSatisfactions());
CriterionSatisfaction posteriorSameCriterion = getNextSameCriterion
(criterion, newSatisfaction,this.getCriterionSatisfactions());
boolean canAdd = ((previousSameCriterion == null ||
!previousSameCriterion.overlapsWith(interval)) &&
( posteriorSameCriterion == null ||
!posteriorSameCriterion.overlapsWith(interval)));
if(!canAdd) {
return false;
}
if (type.isAllowSimultaneousCriterionsPerResource()){
return true;
}
CriterionSatisfaction previous = getPrevious(criterionWithItsType
.getType(), newSatisfaction,this.getCriterionSatisfactions());
CriterionSatisfaction posterior = getNext(criterionWithItsType
.getType(), newSatisfaction,this.getCriterionSatisfactions());
return (previous == null || !previous.overlapsWith(interval)) &&
( posterior == null || !posterior.overlapsWith(interval));
}
public boolean canAddSatisfaction(ICriterionType<?> type,
CriterionSatisfaction satisfaction) {
EnsureSatisfactionIsCorrect ensureSatisfactionIsCorrect = new EnsureSatisfactionIsCorrect(
this, type, satisfaction);
return ensureSatisfactionIsCorrect.canAddSatisfaction();
}
private CriterionSatisfaction getNext(ICriterionType<?> type,
CriterionSatisfaction newSatisfaction,Set<CriterionSatisfaction> list) {
List<CriterionSatisfaction> ordered = query().from(type).result(list);
int position = findPlace(ordered, newSatisfaction);
CriterionSatisfaction next = position != ordered.size() ? ordered
.get(position) : null;
return next;
}
private CriterionSatisfaction getPrevious(ICriterionType<?> type,
CriterionSatisfaction newSatisfaction,Set<CriterionSatisfaction> list) {
List<CriterionSatisfaction> ordered = query().from(type).result(list);
int position = findPlace(ordered, newSatisfaction);
CriterionSatisfaction previous = position > 0 ? ordered
.get(position - 1) : null;
return previous;
}
private CriterionSatisfaction getNextSameCriterion(Criterion criterion,
CriterionSatisfaction newSatisfaction,Set<CriterionSatisfaction> list) {
List<CriterionSatisfaction> ordered = query().from(criterion).result(list);
int position = findPlace(ordered, newSatisfaction);
CriterionSatisfaction next = position != ordered.size() ? ordered
.get(position) : null;
return next;
}
private CriterionSatisfaction getPreviousSameCriterion(Criterion criterion,
CriterionSatisfaction newSatisfaction,Set<CriterionSatisfaction> list) {
List<CriterionSatisfaction> ordered = query().from(criterion).result(list);
int position = findPlace(ordered, newSatisfaction);
CriterionSatisfaction previous = position > 0 ? ordered
.get(position - 1) : null;
return previous;
}
public void removeCriterionSatisfaction(CriterionSatisfaction satisfaction) {
criterionSatisfactions.remove(satisfaction);
}
public boolean contains(CriterionSatisfaction satisfaction) {
return criterionSatisfactions.contains(satisfaction);
}
/**
* @throws IllegalArgumentException in case of overlapping
*/
public void checkNotOverlaps() {
checkNotOverlaps(getRelatedTypes());
}
private List<CriterionType> getRelatedTypes() {
List<CriterionType> types = new ArrayList<CriterionType>();
for (CriterionSatisfaction criterionSatisfaction : this.getCriterionSatisfactions()) {
types.add(criterionSatisfaction.getCriterion().getType());
}
return types;
}
/**
* @throws IllegalArgumentException in case of overlapping
*/
private void checkNotOverlaps(List<CriterionType> types) {
for (CriterionType criterionType : types) {
List<CriterionSatisfaction> satisfactions = query().from(
criterionType).result();
ListIterator<CriterionSatisfaction> listIterator = satisfactions
.listIterator();
while (listIterator.hasNext()) {
CriterionSatisfaction current = listIterator.next();
CriterionSatisfaction previous = getPrevious(listIterator);
CriterionSatisfaction next = getNext(listIterator);
if (previous != null) {
checkNotOverlaps(previous, current);
}
if (next != null) {
checkNotOverlaps(current, next);
}
}
}
}
/**
* IMPORTANT: <code>before</code> and <code>after</code> must refer to the
* same <code>CriterionType</code>
*
* @throws IllegalArgumentException in case of overlapping
*/
private void checkNotOverlaps(CriterionSatisfaction before,
CriterionSatisfaction after) {
CriterionType criterionType = before.getCriterion().getType();
/*
* If criterion satisfactions refer to the same Criterion, they must not
* overlap (regardless of its CriterionType allows simultaneous
* criterion satisfactions per resource).
*/
if (before.getCriterion().equals(after.getCriterion()) &&
!before.goesBeforeWithoutOverlapping(after)) {
throw new IllegalArgumentException(createOverlapsMessage(before,
after));
}
/*
* If CriterionType does not allow simultaneous criterion satisfactions
* per resource, criterion satisfactions must not overlap (regardless
* of they refer to different Criterion objects).
*/
if (!criterionType.isAllowSimultaneousCriterionsPerResource() &&
!before.goesBeforeWithoutOverlapping(after)) {
throw new IllegalArgumentException(createOverlapsMessage(before,
after));
}
}
private String createOverlapsMessage(CriterionSatisfaction before,
CriterionSatisfaction after) {
return new StringBuilder("the satisfaction").append(before).append(
"overlaps with").append(after).toString();
}
private CriterionSatisfaction getNext(
ListIterator<CriterionSatisfaction> listIterator) {
if (listIterator.hasNext()) {
CriterionSatisfaction result = listIterator.next();
listIterator.previous();
return result;
}
return null;
}
private CriterionSatisfaction getPrevious(
ListIterator<CriterionSatisfaction> listIterator) {
listIterator.previous();
try {
if (listIterator.hasPrevious()) {
CriterionSatisfaction result = listIterator.previous();
listIterator.next();
return result;
}
return null;
} finally {
listIterator.next();
}
}
public void setCalendar(ResourceCalendar calendar) {
this.calendar = calendar;
if (calendar != null) {
calendar.setResource(this);
}
}
public ResourceCalendar getCalendar() {
return calendar;
}
public void setResourceCalendar(String calendarCode)
throws InstanceNotFoundException, MultipleInstancesException {
ResourceCalendar calendar;
if (StringUtils.isBlank(calendarCode)) {
calendar = Registry.getConfigurationDAO().getConfiguration().
getDefaultCalendar().newDerivedResourceCalendar();
} else {
BaseCalendar baseCalendar = Registry.getBaseCalendarDAO()
.findByCode(calendarCode);
calendar = baseCalendar.newDerivedResourceCalendar();
}
setCalendar(calendar);
}
public EffortDuration getAssignedEffort(LocalDate localDate) {
return DayAssignment.sum(getAssignmentsForDay(localDate));
}
public EffortDuration getAssignedDurationDiscounting(
Map<Long, Set<BaseEntity>> allocationsFromWhichDiscountHours,
LocalDate day) {
EffortDuration result = zero();
for (DayAssignment dayAssignment : getAssignmentsForDay(day)) {
if ( !dayAssignment.belongsToSomeOf(allocationsFromWhichDiscountHours) ) {
result = result.plus(dayAssignment.getDuration());
}
}
return result;
}
public void addNewAssignments(Collection<? extends DayAssignment> assignments) {
Validate.notNull(assignments);
Validate.noNullElements(assignments);
clearCachedData();
this.dayAssignments.addAll(assignments);
}
public void removeAssignments(Collection<? extends DayAssignment> assignments) {
Validate.noNullElements(assignments);
clearCachedData();
this.dayAssignments.removeAll(assignments);
}
public List<DayAssignment> getAssignments() {
return dayAssignmentsState.getAssignments();
}
public void useScenario(Scenario scenario) {
dayAssignmentsState = new OnSpecifiedScenario(scenario);
}
public int getTotalWorkHours(LocalDate start, LocalDate end) {
return getTotalWorkHours(start, end, null);
}
public int getTotalWorkHours(LocalDate start, LocalDate endExclusive,
ICriterion criterion) {
return getTotalEffortFor(IntraDayDate.startOfDay(start),
IntraDayDate.startOfDay(endExclusive), criterion)
.roundToHours();
}
public EffortDuration getTotalEffortFor(IntraDayDate startInclusive,
IntraDayDate endExclusive) {
return getTotalEffortFor(startInclusive, endExclusive, null);
}
public EffortDuration getTotalEffortFor(IntraDayDate startInclusive,
IntraDayDate endExclusive, ICriterion criterion) {
return getTotalEffortFor(getCalendarOrDefault(), startInclusive,
endExclusive, criterion);
}
public ICalendar getCalendarOrDefault() {
return getCalendar() != null ? getCalendar() : SameWorkHoursEveryDay
.getDefaultWorkingDay();
}
private EffortDuration getTotalEffortFor(final ICalendar calendar,
IntraDayDate startInclusive, IntraDayDate endExclusive,
final ICriterion criterionToSatisfy) {
Iterable<PartialDay> daysBetween = startInclusive
.daysUntil(endExclusive);
return EffortDuration.sum(daysBetween, new IEffortFrom<PartialDay>() {
@Override
public EffortDuration from(PartialDay current) {
EffortDuration capacityCurrent = calendar
.getCapacityOn(current);
if (capacityCurrent != null
&& (criterionToSatisfy == null || satisfiesCriterionAt(
criterionToSatisfy, current.getDate()))) {
return capacityCurrent;
}
return zero();
}
});
}
private boolean satisfiesCriterionAt(ICriterion criterionToSatisfy,
LocalDate current) {
return criterionToSatisfy.isSatisfiedBy(this, current);
}
public void addUnvalidatedSatisfaction(CriterionSatisfaction
criterionSatisfaction) {
criterionSatisfactions.add(criterionSatisfaction);
}
public void addSatisfactions(Set<CriterionSatisfaction> addlist) throws ValidationException {
//Create a newList with new Satisfactions and the old satisfactions
Set<CriterionSatisfaction> newList = new HashSet<CriterionSatisfaction>(addlist);
for(CriterionSatisfaction satisfaction : criterionSatisfactions){
if( !newList.contains(satisfaction) ){
newList.add(satisfaction);
}
}
//Create a activeList with not eliminated Satifaction
Set<CriterionSatisfaction> activeList = new HashSet<CriterionSatisfaction>();
for(CriterionSatisfaction satisfaction : addlist){
if( !satisfaction.isIsDeleted() ){
activeList.add(satisfaction);
}
}
validateSatisfactions(activeList);
criterionSatisfactions.clear();
criterionSatisfactions.addAll(newList);
}
private void validateSatisfactions(Set<CriterionSatisfaction> satisfactions) throws ValidationException {
for (CriterionSatisfaction satisfaction : satisfactions) {
final Set<CriterionSatisfaction> remainingSatisfactions = new HashSet<CriterionSatisfaction>();
remainingSatisfactions.addAll(satisfactions);
remainingSatisfactions.remove(satisfaction);
validateSatisfaction(satisfaction, remainingSatisfactions);
}
}
private void validateSatisfaction(CriterionSatisfaction satisfaction, Set<CriterionSatisfaction> satisfactions)
throws ValidationException {
if ( !canAddSatisfaction(satisfaction, satisfactions) ) {
String message = getReasonForNotAddingSatisfaction(satisfaction.getCriterion().getType());
throw new ValidationException(invalidValue(message, "resource", this, satisfaction));
}
}
private boolean canAddSatisfaction(CriterionSatisfaction satisfaction, Set<CriterionSatisfaction> satisfactions) {
final ICriterionType<?> type = satisfaction.getCriterion().getType();
return canAddSatisfaction(type, satisfaction, satisfactions);
}
public boolean satisfiesCriterions(Collection<? extends ICriterion> criterions) {
ICriterion compositedCriterion = CriterionCompounder.buildAnd(criterions).getResult();
return compositedCriterion.isSatisfiedBy(this);
}
public boolean satisfiesCriterionsAtSomePoint(Collection<? extends Criterion> criterions) {
AvailabilityTimeLine availability = AvailabilityCalculator.getCriterionsAvailabilityFor(criterions, this);
return !availability.getValidPeriods().isEmpty();
}
@Valid
public Set<ResourcesCostCategoryAssignment> getResourcesCostCategoryAssignments() {
return resourcesCostCategoryAssignments;
}
public ResourcesCostCategoryAssignment
getResourcesCostCategoryAssignmentByCode(String code)
throws InstanceNotFoundException {
if (StringUtils.isBlank(code)) {
throw new InstanceNotFoundException(code,
ResourcesCostCategoryAssignment.class.getName());
}
for (ResourcesCostCategoryAssignment i :
resourcesCostCategoryAssignments) {
if (i.getCode().equalsIgnoreCase(StringUtils.trim(code))) {
return i;
}
}
throw new InstanceNotFoundException(code,
ResourcesCostCategoryAssignment.class.getName());
}
public void addResourcesCostCategoryAssignment(ResourcesCostCategoryAssignment assignment) {
resourcesCostCategoryAssignments.add(assignment);
if (assignment.getResource() != this) {
assignment.setResource(this);
}
}
public void addUnvalidatedResourcesCostCategoryAssignment(
ResourcesCostCategoryAssignment assignment) {
resourcesCostCategoryAssignments.add(assignment);
}
public void removeResourcesCostCategoryAssignment(ResourcesCostCategoryAssignment assignment) {
resourcesCostCategoryAssignments.remove(assignment);
if (assignment.getResource() == this) {
assignment.setResource(null);
}
}
@AssertTrue(message="Some criterion satisfactions overlap in time")
public boolean isCriterionSatisfactionsOverlappingConstraint() {
/*
* Check if time intervals in criterion satisfactions are correct in
* isolation. If not, it does not make sense to check criterion
* satisfaction overlapping.
*/
for (CriterionSatisfaction i : getCriterionSatisfactions()) {
if ( !(i.isStartDateSpecified() && i.isPositiveTimeInterval()) ) {
return true;
}
}
/*
* Check assignment overlapping.
*/
try {
checkNotOverlaps();
} catch (IllegalArgumentException e) {
return false;
}
return true;
}
@AssertFalse(message="Some cost category assignments overlap in time")
public boolean isAssignmentsOverlappingConstraint() {
/*
* Check if time intervals in cost assignments are correct in isolation.
* If not, it does not make sense to check assignment overlapping.
*/
for (ResourcesCostCategoryAssignment each : getResourcesCostCategoryAssignments()) {
if ( !(each.isInitDateSpecified() && each.isPositiveTimeInterval()) ) {
return false;
}
}
/*
* Check assignment overlapping.
*/
List<ResourcesCostCategoryAssignment> assignmentsList =
new ArrayList<ResourcesCostCategoryAssignment>();
assignmentsList.addAll(getResourcesCostCategoryAssignments());
try {
CostCategory.validateCostCategoryOverlapping(assignmentsList);
} catch (ValidationException e) {
return true;
}
return false;
}
public abstract ResourceEnum getType();
public boolean isVirtual() {
return false;
}
@AssertTrue(message="there exist criterion satisfactions referring to " +
"criterion types not applicable to this resource")
public boolean isCriterionSatisfactionsWithCorrectTypeConstraint() {
for (CriterionSatisfaction c : getCriterionSatisfactions()) {
if (!isCriterionSatisfactionOfCorrectType(c)) {
return false;
}
}
return true;
}
@AssertTrue(message="criterion satisfaction codes must be unique inside " +
"a resource")
public boolean isNonRepeatedCriterionSatisfactionCodesConstraint() {
return getFirstRepeatedCode(criterionSatisfactions) == null;
}
@AssertTrue(message="resource cost category assignments codes must be " +
"unique inside a resource")
public boolean isNonRepeatedResourcesCostCategoryAssignmentCodesConstraint() {
return getFirstRepeatedCode(resourcesCostCategoryAssignments) == null;
}
protected abstract boolean isCriterionSatisfactionOfCorrectType(
CriterionSatisfaction c);
protected IResourceDAO getIntegrationEntityDAO() {
return Registry.getResourceDAO();
}
public Boolean isLimitingResource() {
return (resourceType == ResourceType.LIMITING_RESOURCE);
}
public ResourceType getResourceType() {
return resourceType;
}
public void setResourceType(ResourceType resourceType) {
this.resourceType = resourceType;
}
public LimitingResourceQueue getLimitingResourceQueue() {
return limitingResourceQueue;
}
public void setLimitingResourceQueue(LimitingResourceQueue limitingResourceQueue) {
limitingResourceQueue.setResource(this);
this.limitingResourceQueue = limitingResourceQueue;
}
@Override
public int compareTo(Resource resource) {
return this.getShortDescription().compareToIgnoreCase(
resource.getShortDescription());
}
@AssertTrue(message = "You have exceeded the maximum limit of resources")
public boolean isMaxResourcesConstraint() {
return Registry.getTransactionService().runOnAnotherReadOnlyTransaction(new IOnTransaction<Boolean>() {
@Override
public Boolean execute() {
Configuration configuration = Registry.getConfigurationDAO().getConfiguration();
if ( configuration == null ) {
return true;
}
Integer maxResources = configuration.getMaxResources();
if ( maxResources != null && maxResources > 0 ) {
List<Resource> resources = Registry.getResourceDAO().findAll();
int resourcesNumber = resources.size();
if ( isNewObject() ) {
resourcesNumber++;
}
if ( resourcesNumber > maxResources ) {
return false;
}
}
return true;
}
});
}
public boolean isActiveBetween(LocalDate startDate, LocalDate endDate) {
return calendar.isActiveBetween(startDate, endDate);
}
}