/* * 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.Comparator; import java.util.List; import org.apache.commons.lang3.Validate; public class ShareDivision { public static ShareDivision create(Collection<? extends Share> shares) { return new ShareDivision(shares); } private static class ShareWrapper implements Comparable<ShareWrapper> { private Share share; private int originalPosition; public ShareWrapper(Share share, int originalPosition) { this.share = share; this.originalPosition = originalPosition; } @Override public int compareTo(ShareWrapper other) { long thisHours = share.getHours(); long otherHours = other.share.getHours(); return Long.signum(thisHours - otherHours); } @Override public boolean equals(Object obj) { if (obj instanceof ShareWrapper) { ShareWrapper other = (ShareWrapper) obj; return getHours() == other.getHours(); } return false; } @Override public int hashCode() { return getHours(); } public static List<ShareWrapper> wrap(List<Share> shares) { List<ShareWrapper> result = new ArrayList<ShareWrapper>(); int i = 0; for (Share share : shares) { result.add(new ShareWrapper(share, i)); i++; } return result; } public boolean haveSameHours(ShareWrapper other) { return getHours() == other.getHours(); } int getHours() { return this.share.getHours(); } void add(int hours) { this.share = share.plus(hours); } public static void sortByOriginalPosition(List<ShareWrapper> bucket) { Collections.sort(bucket, new Comparator<ShareWrapper>() { @Override public int compare(ShareWrapper o1, ShareWrapper o2) { return o1.originalPosition - o2.originalPosition; } }); } } private final List<Share> shares; private ShareDivision(Collection<? extends Share> shares) { Validate.notNull(shares); Validate.noNullElements(shares); this.shares = new ArrayList<Share>(shares); } public List<Share> getShares() { return Collections.unmodifiableList(shares); } public ShareDivision plus(final int increase) { int remainderIncrease = increase; List<ShareWrapper> wrapped = ShareWrapper.wrap(shares); Collections.sort(wrapped); int i = 0; while (i < wrapped.size()) { if (remainderIncrease == 0) { break; } int nextBigger = findNextBigger(wrapped, i); FillingBucket bucket = fillingBuckectFor(wrapped, nextBigger, remainderIncrease); bucket.doTheDistribution(); i = nextBigger; remainderIncrease = remainderIncrease - bucket.getIncreaseDone(); assert remainderIncrease >= 0; } assert remainderIncrease == 0 || shares.isEmpty() : "all is assigned"; return ShareDivision.create(fromWrappers(wrapped)); } private ArrayList<Share> fromWrappers(List<ShareWrapper> wrapped) { ArrayList<Share> newShares = new ArrayList<Share>(shares.size()); for (int i = 0; i < wrapped.size(); i++) { newShares.add(null); } for (ShareWrapper shareWrapper : wrapped) { newShares.set(shareWrapper.originalPosition, shareWrapper.share); } return newShares; } private static class FillingBucket { private List<ShareWrapper> bucket; private int increment; private FillingBucket(List<ShareWrapper> bucket, int increment) { this.bucket = bucket; this.increment = increment; } public void doTheDistribution() { int incrementPerShare = increment / bucket.size(); int remainder = increment % bucket.size(); if (remainder > 0) { ShareWrapper.sortByOriginalPosition(this.bucket); // so the first original elements receive the remainder } for (ShareWrapper wrapper : bucket) { wrapper.add(incrementPerShare + Math.min(1, remainder)); if (remainder > 0) { remainder--; } } } int getIncreaseDone() { return increment; } } private static int findNextBigger(List<ShareWrapper> wrappers, int start) { ShareWrapper startWrapper = wrappers.get(start); for (int i = start + 1; i < wrappers.size(); i++) { ShareWrapper current = wrappers.get(i); if (!startWrapper.haveSameHours(current)) { return i; } } return wrappers.size(); } private static FillingBucket fillingBuckectFor(List<ShareWrapper> wrappers, int end, int remaining) { int hoursToDistribute = end == wrappers.size() ? remaining : (int) Math .min(hoursNeededToBeEqual(wrappers, 0, end), remaining); return new FillingBucket(wrappers.subList(0, end), hoursToDistribute); } private static long hoursNeededToBeEqual(List<ShareWrapper> wrappers, int startInclusive, int endExclusive) { ShareWrapper nextLevel = wrappers.get(endExclusive); ShareWrapper currentLevel = wrappers.get(startInclusive); long currentLevelHours = (long) currentLevel.getHours(); long difference = nextLevel.getHours() - currentLevelHours; // difference must be long in order to avoid integer overflow assert difference > 0; return (endExclusive - startInclusive) * difference; } @Override public String toString() { return shares.toString(); } public int[] to(ShareDivision newDivison) { Validate.isTrue(shares.size() == newDivison.shares.size()); int[] result = new int[shares.size()]; for (int i = 0; i < result.length; i++) { result[i] = newDivison.shares.get(i).getHours() - shares.get(i).getHours(); } return result; } }