diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/allocationalgorithms/Distributor.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/allocationalgorithms/Distributor.java
new file mode 100644
index 000000000..2e9d99309
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/allocationalgorithms/Distributor.java
@@ -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 .
+ */
+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
+ */
+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 normalCapacityShares;
+
+ private final List limitedOverloadShares;
+
+ private final List unlimitedOverloadShares;
+
+ private final List> phases = new ArrayList>();
+
+ 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 createNormalCapacityShares(Capacity[] capacities) {
+ List result = new ArrayList();
+ for (Capacity each : capacities) {
+ result.add(createNormalCapacityShare(each));
+ }
+ return result;
+ }
+
+ private List createOverloadShares(Capacity[] capacities) {
+ List result = new ArrayList();
+ 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(){
+
+ @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 createUnlimitedShares(Capacity[] capacities) {
+ List result = new ArrayList();
+ for (Capacity each : capacities) {
+ result.add(each.isOverAssignableWithoutLimit() ? new Share(0)
+ : noSpaceAvailable());
+ }
+ return result;
+ }
+
+ public List distribute(EffortDuration effort) {
+ EffortDuration[] result = new EffortDuration[capacities.length];
+ Arrays.fill(result, EffortDuration.zero());
+
+ for (List 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 shares) {
+ ShareDivision division = ShareDivision.create(shares);
+ return fromSecondsToDurations(division.to(division.plus(effort
+ .getSeconds())));
+ }
+
+ private List asList(EffortDuration[] acc) {
+ return new ArrayList(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() {
+
+ @Override
+ public EffortDuration from(EffortDuration each) {
+ return each;
+ }});
+ }
+
+}
diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/allocationalgorithms/DistributorTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/allocationalgorithms/DistributorTest.java
new file mode 100644
index 000000000..4b14ddfeb
--- /dev/null
+++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/planner/entities/allocationalgorithms/DistributorTest.java
@@ -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> hasEfforts(
+ final EffortDuration... efforts) {
+ return new BaseMatcher>() {
+
+ @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));
+ }
+ };
+ }
+
+}