/*
* This file is part of the TimeFinder project.
* Visit http://www.timefinder.de for more information.
* Copyright (c) 2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.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.timefinder.algo;
import de.timefinder.algo.constraint.RasterConstraint;
import de.timefinder.data.Resource;
import de.timefinder.data.algo.Assignment;
import de.timefinder.data.set.DayRaster;
import de.timefinder.data.set.EventRaster;
import de.timefinder.data.set.EventRasterOneTreeMapImpl;
import de.timefinder.data.set.RasterCollection;
import javolution.util.FastMap;
import javolution.util.FastSet;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* This class holds a matrix of Events to Events.
* The returned Events of getConflicting cannot be at the same timeslot as
* the specified one.
* E.g. resource R1 requires events A and B; resource R2 requires B and C; resource R2 requires A, B and D
* => getConflicting(A) = {B, D}
* => getConflicting(B) = {A, C, D}
* => getConflicting(C) = {B}
* <p/>
* HINT: the conflictMatrix won't be cloned if the Periode is cloned.
*
* @author Peter Karich, peat_hal 'at' users 'dot' sourceforge 'dot' net
*/
public class ConflictMatrix {
private Map<Assignment, Set<Assignment>> conflictMatrix = new FastMap<Assignment, Set<Assignment>>();
private Map<Assignment, RasterEntry> rasterMap = new FastMap<Assignment, RasterEntry>();
private DayRaster dayRaster;
private int slotsPerWeek;
private boolean enabled = true;
public ConflictMatrix(int slotsPerWeek, int slotsPerDay) {
this.slotsPerWeek = slotsPerWeek;
// use for all events the same dayRaster
dayRaster = new DayRaster(slotsPerDay, slotsPerWeek);
}
public Set<Assignment> initFromResources(Map<? extends Resource, Set<Assignment>> assignments) {
for (Entry<? extends Resource, Set<Assignment>> resEntry : assignments.entrySet()) {
for (Assignment event : resEntry.getValue()) {
Set<Assignment> tmpSet = getConflictingAssignments(event);
for (Assignment e2 : resEntry.getValue()) {
// add event even if e == e2!
tmpSet.add(e2);
}
}
}
initAssignments(conflictMatrix.keySet());
return conflictMatrix.keySet();
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Initialize raster for all events
*/
public void initAssignments(Collection<Assignment> events) {
for (Assignment ass : events) {
RasterCollection combinedRaster = new RasterCollection();
RasterConstraint rc = ass.getEvent().getConstraint(RasterConstraint.class);
if (rc != null)
combinedRaster.add(rc.getRaster().getForbidden());
EventRaster eventRaster = new EventRasterOneTreeMapImpl(slotsPerWeek);
// EventRaster eventRaster = new EventRasterTreeMapImpl(slotsPerWeek);
combinedRaster.add(eventRaster);
combinedRaster.add(dayRaster);
rasterMap.put(ass, new RasterEntry(eventRaster, combinedRaster));
}
}
/**
* @return a collection of all events which must NOT be in the same
* timeslot as the specified Event ev.
*/
public Set<Assignment> getConflictingAssignments(Assignment ev) {
Set<Assignment> set = conflictMatrix.get(ev);
if (set == null) {
set = FastSet.newInstance();
conflictMatrix.put(ev, set);
}
return set;
}
/**
* @return the latest raster for the specified event. I.e. in the
* slots for which the raster is true the specified event cannot happen,
* because other conflicting events are already assigned there.
*/
public RasterCollection getConflictingRaster(Assignment ass) {
return getRasterEntry(ass).rasterCollection;
}
private RasterEntry getRasterEntry(Assignment event) {
return rasterMap.get(event);
}
/**
* Updates the start value in the EventRaster of all conflicting events
* of the specified Event event
*/
public void add(Assignment ass) {
if (!enabled)
return;
if (ass.getStart() < 0)
throw new IllegalArgumentException("Start cannot be negative! (" + ass + ")");
RasterEntry entry = getRasterEntry(ass);
if (entry.eventRaster.getRoot() != null)
throw new IllegalStateException("rasters root event must be null while newly adding " + ass + " root:" + entry.eventRaster.getRoot());
entry.eventRaster.setRoot(ass);
// add conflicting events to the raster but only those which were already added to this matrix
for (Assignment conflictingEvent : getConflictingAssignments(ass)) {
if (conflictingEvent == ass)
continue;
RasterEntry cEntry = rasterMap.get(conflictingEvent);
if (cEntry != null) { // event is already assigned to rasterMap
if (!cEntry.eventRaster.add(ass))
throw new UnsupportedOperationException(ass + " should be added to raster at "
+ " but wasn't. " + cEntry.eventRaster);
}
}
}
/**
* This method removes the specified event with its start and duration
* from all the conflicting events' rasters.
*/
public void remove(Assignment ass) {
if (!enabled)
return;
for (Assignment conflictingEvent : getConflictingAssignments(ass)) {
if (conflictingEvent == ass)
continue;
RasterEntry tmpEntry = rasterMap.get(conflictingEvent);
if (tmpEntry != null) {
if (!tmpEntry.eventRaster.remove(ass))
throw new UnsupportedOperationException("Removing should be possible! "
+ ass + " conflictingEvent:" + conflictingEvent + " raster:" + tmpEntry.eventRaster);
}
}
// now it does NOT matter where conflicting events will be assigned
RasterEntry e = rasterMap.get(ass);
e.eventRaster.setRoot(null);
}
public Set<Assignment> calculateConflictingAssignments(Assignment currentEvent, int startTime) {
return getRasterEntry(currentEvent).eventRaster.getConflictingAssignments(
startTime, currentEvent.getEvent().getDuration());
}
private static class RasterEntry {
private EventRaster eventRaster;
private RasterCollection rasterCollection;
public RasterEntry(EventRaster eventRaster, RasterCollection rasterCollection) {
this.eventRaster = eventRaster;
this.rasterCollection = rasterCollection;
}
@Override
public String toString() {
return rasterCollection.toString();
}
// public boolean isAssigned() {
// return eventRaster.getRootEvent() != null;
// }
}
}