Add class to distribute an EffortDuration considering the capacities

FEA: ItEr75S04BugFixing
This commit is contained in:
Óscar González Fernández 2011-08-11 18:19:50 +02:00
parent d7a59bd9f1
commit 5f5275f62f
2 changed files with 312 additions and 0 deletions

View file

@ -0,0 +1,207 @@
/*
* This file is part of NavalPlan
*
* 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.navalplanner.business.planner.entities.allocationalgorithms;
import static org.navalplanner.business.workingday.EffortDuration.seconds;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.lang.Validate;
import org.navalplanner.business.calendars.entities.Capacity;
import org.navalplanner.business.planner.entities.Share;
import org.navalplanner.business.planner.entities.ShareDivision;
import org.navalplanner.business.workingday.EffortDuration;
import org.navalplanner.business.workingday.EffortDuration.IEffortFrom;
/**
* Distributes an EffortDuration among several capacities. It respects the extra
* hours requirements of the {@link Capacity capacities} and distributes the
* effort evenly.
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public class Distributor {
public static Distributor among(Capacity... capacities) {
return among(Arrays.asList(capacities));
}
public static Distributor among(Collection<? extends Capacity> capacities) {
Validate.noNullElements(capacities);
return new Distributor(capacities.toArray(new Capacity[0]));
}
private final Capacity[] capacities;
private final List<Share> normalCapacityShares;
private final List<Share> limitedOverloadShares;
private final List<Share> unlimitedOverloadShares;
private final List<List<Share>> phases = new ArrayList<List<Share>>();
private Distributor(Capacity[] capacities) {
Validate.notNull(capacities);
this.capacities = capacities;
this.normalCapacityShares = createNormalCapacityShares(capacities);
this.limitedOverloadShares = createOverloadShares(capacities);
this.unlimitedOverloadShares = createUnlimitedShares(capacities);
this.phases.add(normalCapacityShares);
this.phases.add(limitedOverloadShares);
this.phases.add(this.unlimitedOverloadShares);
}
private static List<Share> createNormalCapacityShares(Capacity[] capacities) {
List<Share> result = new ArrayList<Share>();
for (Capacity each : capacities) {
result.add(createNormalCapacityShare(each));
}
return result;
}
private List<Share> createOverloadShares(Capacity[] capacities) {
List<Share> result = new ArrayList<Share>();
EffortDuration maxExtraEffort = getMaxExtraEffort(capacities);
for (Capacity each : capacities) {
result.add(maxExtraEffort == null ? noSpaceAvailable()
: createOverloadShare(each, maxExtraEffort));
}
return result;
}
private EffortDuration getMaxExtraEffort(Capacity[] capacities) {
if (capacities.length == 0) {
return null;
}
Capacity max = Collections.max(Arrays.asList(capacities), new Comparator<Capacity>(){
@Override
public int compare(Capacity o1, Capacity o2) {
if (o1.getAllowedExtraEffort() == o2.getAllowedExtraEffort()) {
return 0;
} else if (o1.getAllowedExtraEffort() == null) {
return -1;
} else if (o2.getAllowedExtraEffort() == null) {
return 1;
}
return o1.getAllowedExtraEffort().compareTo(
o2.getAllowedExtraEffort());
}
});
return max.getAllowedExtraEffort();
}
private static Share createNormalCapacityShare(Capacity each) {
return new Share(-each.getStandardEffort().getSeconds());
}
private Share createOverloadShare(Capacity each,
EffortDuration maxExtraEffort) {
if (each.getAllowedExtraEffort() == null && !each.isOverAssignableWithoutLimit()) {
return noSpaceAvailable();
}
EffortDuration effort = each.getAllowedExtraEffort() != null ? each
.getAllowedExtraEffort() : maxExtraEffort;
return new Share(-effort.getSeconds());
}
private Share noSpaceAvailable() {
return new Share(Integer.MAX_VALUE);
}
private List<Share> createUnlimitedShares(Capacity[] capacities) {
List<Share> result = new ArrayList<Share>();
for (Capacity each : capacities) {
result.add(each.isOverAssignableWithoutLimit() ? new Share(0)
: noSpaceAvailable());
}
return result;
}
public List<EffortDuration> distribute(EffortDuration effort) {
EffortDuration[] result = new EffortDuration[capacities.length];
Arrays.fill(result, EffortDuration.zero());
for (List<Share> shares : phases) {
EffortDuration remaining = effort.minus(sum(result));
if (remaining.isZero()) {
return asList(result);
}
result = limitByCapacities(sum(result,
distribute(remaining, shares)));
}
return asList(result);
}
private EffortDuration[] sum(EffortDuration[] a, EffortDuration[] b) {
EffortDuration[] result = new EffortDuration[a.length];
for (int i = 0; i < result.length; i++) {
result[i] = a[i].plus(b[i]);
}
return result;
}
private EffortDuration[] distribute(EffortDuration effort,
List<Share> shares) {
ShareDivision division = ShareDivision.create(shares);
return fromSecondsToDurations(division.to(division.plus(effort
.getSeconds())));
}
private List<EffortDuration> asList(EffortDuration[] acc) {
return new ArrayList<EffortDuration>(Arrays.asList(acc));
}
private EffortDuration[] limitByCapacities(EffortDuration[] efforts) {
EffortDuration[] result = new EffortDuration[efforts.length];
for (int i = 0; i < efforts.length; i++) {
result[i] = capacities[i].limitDuration(efforts[i]);
}
return result;
}
private EffortDuration[] fromSecondsToDurations(int[] seconds) {
EffortDuration[] result = new EffortDuration[seconds.length];
for (int i = 0; i < result.length; i++) {
result[i] = seconds(seconds[i]);
}
return result;
}
private EffortDuration sum(EffortDuration[] durations) {
return EffortDuration.sum(asList(durations),
new IEffortFrom<EffortDuration>() {
@Override
public EffortDuration from(EffortDuration each) {
return each;
}});
}
}

View file

@ -0,0 +1,105 @@
package org.navalplanner.business.test.planner.entities.allocationalgorithms;
import static org.junit.Assert.assertThat;
import static org.navalplanner.business.workingday.EffortDuration.hours;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.navalplanner.business.calendars.entities.Capacity;
import org.navalplanner.business.planner.entities.allocationalgorithms.Distributor;
import org.navalplanner.business.workingday.EffortDuration;
public class DistributorTest {
@Test
public void theEffortIsDistributedEvenly() {
Distributor distributor = Distributor.among(Capacity.create(hours(8)),
Capacity.create(hours(8)));
assertThat(distributor.distribute(hours(16)),
hasEfforts(hours(8), hours(8)));
assertThat(distributor.distribute(hours(8)),
hasEfforts(hours(4), hours(4)));
}
@Test
public void ifNoOverassignationAllowedNotAllIsDistributed() {
Distributor distributor = Distributor.among(Capacity.create(hours(8))
.notOverAssignableWithoutLimit(), Capacity.create(hours(8))
.notOverAssignableWithoutLimit());
assertThat(distributor.distribute(hours(18)),
hasEfforts(hours(8), hours(8)));
}
@Test
public void theOverAssignableCapacityGetsTheRest() {
Distributor distributor = Distributor.among(Capacity.create(hours(8))
.notOverAssignableWithoutLimit(), Capacity.create(hours(8))
.overAssignableWithoutLimit());
assertThat(distributor.distribute(hours(14)),
hasEfforts(hours(7), hours(7)));
assertThat(distributor.distribute(hours(16)),
hasEfforts(hours(8), hours(8)));
assertThat(distributor.distribute(hours(18)),
hasEfforts(hours(8), hours(10)));
}
@Test
public void mixingNotOverAssignableAndOverassignableToALimit() {
Distributor distributor = Distributor.among(Capacity.create(hours(8))
.withAllowedExtraEffort(hours(2)), Capacity.create(hours(8))
.notOverAssignableWithoutLimit());
assertThat(distributor.distribute(hours(16)),
hasEfforts(hours(8), hours(8)));
assertThat(distributor.distribute(hours(17)),
hasEfforts(hours(9), hours(8)));
assertThat(distributor.distribute(hours(18)),
hasEfforts(hours(10), hours(8)));
assertThat(distributor.distribute(hours(19)),
hasEfforts(hours(10), hours(8)));
}
@Test
public void ifNoCapacityItReturnsZeroHours() {
Distributor distributor = Distributor.among(Capacity.create(hours(0))
.notOverAssignableWithoutLimit());
assertThat(distributor.distribute(hours(4)), hasEfforts(hours(0)));
}
private Matcher<List<EffortDuration>> hasEfforts(
final EffortDuration... efforts) {
return new BaseMatcher<List<EffortDuration>>() {
@Override
public boolean matches(Object arg) {
return Arrays.equals(efforts, toArray(arg));
}
private EffortDuration[] toArray(Object value) {
if (value instanceof EffortDuration[]) {
return (EffortDuration[]) value;
}
if (value instanceof List) {
List<?> list = (List<?>) value;
return list.toArray(new EffortDuration[0]);
}
return null;
}
@Override
public void describeTo(Description description) {
description.appendText(Arrays.toString(efforts));
}
};
}
}