/* * Copyright 2009-2012 by KNURT Systeme (http://www.knurt.de) * * Licensed under the Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://creativecommons.org/licenses/by-nc-sa/3.0/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.knurt.fam.core.util.time; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import de.knurt.fam.core.model.persist.FacilityAvailability; import de.knurt.heinzelmann.util.time.TimeFrame; /** * helper class merging overlapping facility time frames. * @author Daniel Oltmanns * @since 0.20090518 */ public class FacilityAvailabilityMerger { /** * return facility time frames merged by priority of facility keys. * if facility key is the same, it is merged by {@link FacilityAvailability#timeStampSet}. * this will destroy all {@link FacilityAvailability#timeStampSet}. * @see #getMergedByTimeStampSet(java.util.List, de.knurt.heinzelmann.util.time.TimeFrame) * @param tfs {@link FacilityAvailability}s to merge * @param fromTo watched time frame (in case of iterations or time frames outside fromTo) * @param priority list of facility keys for priority. the higher the index * of the facility key, the higher the priority. * @return facility time frames merged by priority of facility keys. */ public static List<FacilityAvailability> getMergedByFacilities(List<? extends FacilityAvailability> tfs, TimeFrame fromTo, List<String> priority) { HashMap<String, ArrayList<FacilityAvailability>> tfsOfFacilityKey = new HashMap<String, ArrayList<FacilityAvailability>>(); for (String facilityKey : priority) { tfsOfFacilityKey.put(facilityKey, new ArrayList<FacilityAvailability>()); } for (FacilityAvailability tf : tfs) { tfsOfFacilityKey.get(tf.getFacilityKey()).add(tf); } ArrayList<FacilityAvailability> toMerges = new ArrayList<FacilityAvailability>(); for (int i = 0; i < priority.size(); i++) { List<FacilityAvailability> tmp = getMergedByTimeStampSet(tfsOfFacilityKey.get(priority.get(i)), fromTo); for (FacilityAvailability dtf : tmp) { dtf.setTimeStampSet((long) i); // set position of priority as new time stamp } toMerges.addAll(tmp); } return getMergedByTimeStampSet(toMerges, fromTo); } private static boolean candidateIsHiddenByOtherTf(FacilityAvailability candidate, FacilityAvailability otherTf) { return otherTf.getBasePeriodOfTime().getStart() <= candidate.getBasePeriodOfTime().getStart() && otherTf.getBasePeriodOfTime().getEnd() >= candidate.getBasePeriodOfTime().getEnd(); } private static boolean otherTfIsInCandidate(FacilityAvailability otherTf, FacilityAvailability candidate) { return otherTf.getBasePeriodOfTime().getStart() > candidate.getBasePeriodOfTime().getStart() && otherTf.getBasePeriodOfTime().getEnd() < candidate.getBasePeriodOfTime().getEnd(); } /** * merge and return the time frames. * a newer time frame has priority. facility keys are ignored. * all iterations are splitted and set to ONE_TIME. * the result is unsorted! * @see FacilityAvailability#getTimeStampSet() * @see <a href="./doc-files/timeFrameStrategy_beforeMelting.svg">graphic before mergingm</a> * @see <a href="./doc-files/timeFrameStrategy_AfterMelting.svg">graphic after merging</a> * @param tfs * @param fromTo watched time frame (in case of iterations or time frames outside fromTo) * @return merged time frames. */ public static List<FacilityAvailability> getMergedByTimeStampSet(List<? extends FacilityAvailability> tfs, TimeFrame fromTo) { ArrayList<FacilityAvailability> notIteratedTfs = new ArrayList<FacilityAvailability>(); // set it all to from to and to ONE TIME FacilityAvailabilitys for (FacilityAvailability tf : tfs) { if (tf.overlaps(fromTo)) { for (FacilityAvailability da : tf.getFacilityAvailabilitiesWithNoIteration(fromTo)) { notIteratedTfs.add(da); } } } return getMergedByTimeStampSetIntern(notIteratedTfs, fromTo); } private static ArrayList<FacilityAvailability> getMergedByTimeStampSetIntern(ArrayList<FacilityAvailability> notIteratedTfs, TimeFrame fromTo) { ArrayList<FacilityAvailability> result = new ArrayList<FacilityAvailability>(); if (notIteratedTfs.size() > 1) { // there is something to merge // if a time frame is in another, split and recursion! for (FacilityAvailability candidate : notIteratedTfs) { for (FacilityAvailability otherTf : notIteratedTfs) { if (otherTf.equals(candidate)) { // other time frame is same time frame continue; } if (otherTf.getTimeStampSet().before(candidate.getTimeStampSet())) { // candidate is newer continue; } if (otherTfIsInCandidate(otherTf, candidate)) { FacilityAvailability dtfBefore = candidate.clone(); dtfBefore.getBasePeriodOfTime().setStartEnd(candidate.getBasePeriodOfTime().getStart(), otherTf.getBasePeriodOfTime().getStart()); notIteratedTfs.add(dtfBefore); FacilityAvailability dtfAfter = candidate.clone(); dtfAfter.getBasePeriodOfTime().setStartEnd(otherTf.getBasePeriodOfTime().getEnd(), candidate.getBasePeriodOfTime().getEnd()); notIteratedTfs.add(dtfAfter); notIteratedTfs.remove(candidate); return getMergedByTimeStampSetIntern(notIteratedTfs, fromTo); } } } // compare a candidate to to the rest for (FacilityAvailability candidate : notIteratedTfs) { Calendar newStart = candidate.getBasePeriodOfTime().getCalendarStart(); Calendar newEnd = candidate.getBasePeriodOfTime().getCalendarEnd(); boolean candidateWin = true; // true, if candidate stays in final result (is not hidden by another time frame) for (FacilityAvailability otherTf : notIteratedTfs) { if (otherTf.equals(candidate)) { // other time frame is same time frame continue; } if (otherTf.getTimeStampSet().before(candidate.getTimeStampSet())) { // candidate is newer continue; } if (candidateIsHiddenByOtherTf(candidate, otherTf)) { // candidate is hidden by another time frame and fails candidateWin = false; break; } if (candidate.overlaps(otherTf.getBasePeriodOfTime())) { if (otherTf.getBasePeriodOfTime().getCalendarStart().after(newStart)) { newEnd = otherTf.getBasePeriodOfTime().getCalendarStart(); } if (otherTf.getBasePeriodOfTime().getCalendarEnd().before(newEnd) || otherTf.getBasePeriodOfTime().getCalendarEnd().equals(newEnd)) { newStart = otherTf.getBasePeriodOfTime().getCalendarEnd(); } } if (newStart.getTimeInMillis() >= newEnd.getTimeInMillis()) { // candidate is hidden completely by many other time frames and fails candidateWin = false; break; } } if (candidateWin) { // set new start and end of candidate and add it to the result candidate.getBasePeriodOfTime().setStart(newStart); candidate.getBasePeriodOfTime().setEnd(newEnd); result.add(candidate); } } } else { // nothing to merge result = notIteratedTfs; } return result; } private FacilityAvailabilityMerger() { } }