Package de.timefinder.algo.ncp

Source Code of de.timefinder.algo.ncp.Period

/*
*  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.ncp;

import de.timefinder.algo.ConflictMatrix;
import de.timefinder.algo.constraint.DifferentDayConstraint;
import de.timefinder.algo.constraint.MinGapsConstraint;
import de.timefinder.data.algo.Assignment;
import de.timefinder.data.algo.Solution;
import de.timefinder.algo.constraint.PersonITCRasterConstraint;
import de.timefinder.algo.roomassignment.AssignmentManager;
import de.timefinder.data.Event;
import de.timefinder.data.Location;
import de.timefinder.data.Person;
import de.timefinder.data.algo.Constraint;
import de.timefinder.data.set.RasterCollection;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
import javolution.util.FastMap;
import javolution.util.FastSet;

/**
* This class holds all objects of one periode e.g. one week,
* which are necessary for optimization.
* This class manages the conflictMatrix while assigning events.
*
* @author Peter Karich, peat_hal 'at' users 'dot' sourceforge 'dot' net
*/
public class Period {

    private final ComplexityComparator complexityComparator = new ComplexityComparator();
    private Log logger = LogFactory.getLog(getClass());
    private double depthMultiplier;
    private int maxNodes;
    private int finalDepth;
    private int processedNodes;
    private int noOfSlots;
    private List<Location> allRooms;
    private Map<Person, Set<Assignment>> allPersons;
    /**
     * all assignments (valid and invalid, where start < 0). has to be a list, because of sorting against
     */
    private List<Assignment> allAssignments;
    private Map<Assignment, Set<Assignment>> befores;
    private Map<Assignment, Set<Assignment>> followers;
    private List<Assignment> invalidAssignments;
    private Deque<EventMove> stack;
    private List<AssignmentManager> clusterArray;
    private Random random;
    private ConflictMatrix conflictMatrix;

    public Period(int noOfClusters_,
            List<Assignment> assignments,
            Map<Assignment, Set<Assignment>> befores,
            Map<Assignment, Set<Assignment>> followers,
            List<Location> allRooms_,
            Map<Person, Set<Assignment>> allPersons_,
            ConflictMatrix conflictMatrix_) {

        this.befores = befores;
        this.followers = followers;
        conflictMatrix = conflictMatrix_;
        noOfSlots = noOfClusters_;
        allRooms = allRooms_;
        allPersons = allPersons_;
        allAssignments = assignments;
        stack = new ArrayDeque<EventMove>(noOfClusters_);
        invalidAssignments = new ArrayList<Assignment>();

        clusterArray = new ArrayList<AssignmentManager>(noOfSlots);
        //make sure that all groups exist
        for (int slot = 0; slot < noOfSlots; slot++) {
            clusterArray.add(new AssignmentManager(slot, allRooms));
        }
    }

    public void setRandom(Random random) {
        depthMultiplier = 1.5;
        maxNodes = 15000;
        finalDepth = 4;
        this.random = random;
    }

    Collection<Assignment> getAll() {
        return allAssignments;
    }

    /**
     * @return the number of hard constraints violations.
     */
    public int getHardConstraintsViolations() {
        return invalidAssignments.size();
    }

    // only used in tests
    Collection<EventMove> getEventMoveStack() {
        return stack;
    }

    /**
     * This method tries to injectAt overlapping (colliding) events by
     * swapping with minimal colliding groups and recursivly swaps those events.
     */
    public void compress() {
        stack.clear();

        int noOfUnassignedEvents = getHardConstraintsViolations();

        // We will not try to move the Event into a group if it creates
        // more than depth * <number> = maximal colliding events.
        int depth;

        // Increase calculation intensity (ie. increase depth) if we have only
        // a few events
        // If we increase the depth we will increase the possible maximal
        // colliding events which will be accepted in injectAt()

        // TODO LATER: How to measure the 'pressure' of a week? So that we
        // can determine the faster or most efficient method to injectAt an Event.
        // Every Event should get a depth and a maxCollisions variable.
        // Use complexityMap for this purpose. If injection was
        // successfull -> decrease it (otherwise increasing...)
        // Failing injection could have two reasons:
        // 1. too small depth
        // 2. too less maxColls
        // But if we increase them too much, we will get less main iterations.
        // So if a certain time lmiit is reached we can only increase one and
        // decrease the other (within its ranges)

        int maxCollisions;

        if (noOfUnassignedEvents < 1) {
            return;

        } else if (noOfUnassignedEvents < 6) {
            depth = finalDepth;
            maxCollisions = (int) Math.round(depth * depthMultiplier);

        } else if (noOfUnassignedEvents < 20) {
            depth = 2;
            maxCollisions = 2;

        } else {
            depth = 1;
            maxCollisions = 2;
        }

        // Try to sort unassigned, too
        //Collections.sort(unassignedEvents, complexityComparator);
        List<Assignment> all = new ArrayList<Assignment>(getInvalidAssignments());
        Collections.sort(all, complexityComparator);
//        logger.info("Try to injectAt " + all.size() + " assignments");
        for (Assignment ass : all) {
            processedNodes = 0;
            if (ass.getStart() >= 0)
                throw new IllegalStateException("a previous injection shouldn't happen " + ass);

//            logger.info(ass);
            if (inject(ass, depth, maxCollisions)) {
                if (ass.getStart() < 0)
                    throw new IllegalStateException("Couldn't inject event although it should be ..."
                            + processedNodes + " event:" + ass);
            } else
                ass.setComplexity(ass.getComplexity() + 1);

            stack.clear();
        }
    }

    /**
     * This method tries to inject the specified Assignment into this Period.
     *
     * @param forbiddenStart specifies where currentEvent must not injected
     * @return true if injection was possible.
     */
    boolean inject(Assignment ass, int depth, int maxCollidingEvents) {
        if (depth < 0)
            return false;

//        logger.info("try to injectAt " + event + " depth:" + depth + " maxColl:" + maxCollidingEvents);
        if (processedNodes > maxNodes / 2) {
            if (processedNodes > maxNodes && maxCollidingEvents > 0) {
                // only allow 0; so all collisions are forbidden, i.e. we can't go deeper
                return false;
            }
            if (maxCollidingEvents > 0)
                maxCollidingEvents--;
        }

        int lastStackSize = stack.size();
        int interv[] = getSearchInterval(ass);

        if (interv == null)
            return false;

        RasterCollection raster = conflictMatrix.getConflictingRaster(ass);
        int duration = ass.getEvent().getDuration();

        // doMove() could have become possible, if we moved some events while previous injections!
        for (int startTime = raster.getNextFree(interv[0], duration);
                startTime >= 0 && startTime < interv[1];
                startTime = raster.getNextFree(startTime + 1, duration)) {

            if (doMove(ass, startTime))
                return true;
        }

        // If we will go deeper with injectAt(), the result will always be -1, so we
        // don't need to collect colliding events for this
        if (depth < 1)
            return false;

        // 1. Calculate the colliding events if we would injectAt
        // currentEvent into startTime and save the moved events to stack
        boolean includeEventRaster = false;
        for (int startTime = raster.getNextFree(interv[0], duration, includeEventRaster);
                startTime >= 0 && startTime < interv[1];
                startTime = raster.getNextFree(startTime + 1, duration, includeEventRaster)) {

            if (lastStackSize != stack.size())
                throw new IllegalStateException(" nodes:" + processedNodes
                        + " pointer:" + lastStackSize + " stack.size:" + stack.size() + " " + stack);

            if (injectAt(startTime, ass, depth, maxCollidingEvents))
                return true;
        } // raster loop

        return false;
    }

    /**
     * This method injects the specified assignment into the startTime if
     * possible.
     */
    public boolean injectAt(int startTime, Assignment ass, int depth, int maxCollidingEvents) {
        int lastStackSize = stack.size();
        // Gets conflicting events for the timeslots [startTime, startTime+duration)
        // the order of the conflicting events is only deterministic if we use FastMap
        Set<Assignment> collidingEvents = conflictMatrix.calculateConflictingAssignments(ass, startTime);
        int currentColliding = collidingEvents.size();

        if (currentColliding > maxCollidingEvents) {
            // skip those slots with too many colliding events
            return false;

            // } else if (currentColliding == 0) {
            // happens if there are not enough rooms and currentEvent couldn't
            // be handled in the first for-raster-loop
        } else if (currentColliding > 0) {
            boolean injected = false;

            // Remove the colliding events and try later if injecting elsewhere would be possible (e.g. a room is available).
            for (Assignment tmpAssign : collidingEvents) {
                int lastStart = tmpAssign.getStart();
                if (lastStart < 0)
                    throw new IllegalStateException("assignment cannot be negative here: " + tmpAssign);

                if (!doMove(tmpAssign, -1))
                    throw new IllegalStateException("removing assignment of an event should be always possible: " + tmpAssign);
            }

            if (ass.getStart() >= 0)
                throw new IllegalStateException("ass must be invalid at this point: " + ass);

            if (!doMove(ass, startTime)) {
                // failed to move the currentEvent (e.g. no room available)
                // roll back all events moved in this depth and deeper
                rollback(lastStackSize);
                return false;
            }

            for (Assignment tmpAssignment : collidingEvents) {
                processedNodes++;
                // 2. recursivly injectAt (try to move) all colliding events in a group
                injected = inject(tmpAssignment, depth - 1, maxCollidingEvents - currentColliding);
                if (!injected) {
                    // Insertation was not possible, no rollbackAssign for this
                    // injection necessary, because this was already done in
                    // depth - 1. But we need to revert changes of previously
                    // successfully injected events.
                    break;
                }
            }

            if (injected)
                // SUCCESS !
                return true;

            // Possible cases of the failure:
            // 1. at least one of the colliding events couldn't be injected
            // 2. we reached recursion depth == 0 while injection
            // 3. move of currentEvent failed. this failure was handled earlier: in else { -> rollbackAssign() }

            // FAILURE => ROLLBACK all EventMove's which were successfully
            // injected in 'depth - 1' and try another startTime (continue with raster loop)
            rollback(lastStackSize);
        } // END block "if there are colliding events"
        // else continue;

        return false;
    }

    public List<Assignment> getInvalidAssignments() {
        return invalidAssignments;
    }

    /**
     * This method fixes some events in groupsInWeeks. The rest and
     * overlapping events will be added to the specified canister.
     * After this method call the waste is empty.
     */
    public void prepare4NextIteration(double percentageOfChanges, boolean shuffle) {

        if (getInvalidAssignments().size() < 1) {
            // This method removes the events that have the most
            // soft-constraints violations.
            scPreparation(percentageOfChanges, shuffle);
        } else {
            // This method removes the events randomly - according to the percentage
            randomHCPreparation(percentageOfChanges, shuffle);
        }
    }

    private void scPreparation(double percentage, boolean shuffle) {
        if (shuffle) {
            Collections.shuffle(allAssignments, random);
        } else {
            ConflictsComparator comp = new ConflictsComparator(getSoftConstraintMap());
            Collections.sort(allAssignments, comp);
        }

        int TI_BORDER = (int) Math.round(allAssignments.size() * percentage);
        int size = allAssignments.size();
        int index = random.nextInt(size);
        for (int counter = 0; counter < TI_BORDER; index++, counter++) {
            if (index >= size)
                index = 0;

            moveToUnassignedHead(allAssignments.get(index));
        }
    }

    /**
     * This method removes n events from all clusters; where n depends
     * on percentageOfChanges.
     *
     * @param shuffleBeforeRemoving is false if you don't want that on
     *                              every call of this method we remove nearly the same events
     *                              where percentage increases the length of this 'same Event'
     *                              - list.
     */
    private void randomHCPreparation(double percentageOfChanges, boolean shuffleBeforeRemoving) {
        AssignmentManager assMngr;
        for (int assMngrIndex = clusterArray.size() - 1; assMngrIndex >= 0; assMngrIndex--) {
            assMngr = clusterArray.get(assMngrIndex);
            int size = assMngr.getAll().size();
            if (size == 0)
                continue;

            // clone it for the for-loop!
            List<Assignment> coll = new ArrayList<Assignment>(assMngr.getAll());
            int TI_BORDER = (int) Math.round(size * percentageOfChanges);
            int index = random.nextInt(size);
            for (int counter = 0; counter < TI_BORDER; index++, counter++) {
                if (index >= size)
                    index = 0;

                moveToUnassignedHead(coll.get(index));
            }
        }

//        if (shuffleBeforeRemoving)
//            Collections.shuffle(invalidAssignments, random);
    }

    /**
     * The head should be the most difficult Event.
     */
    public void moveToUnassignedHead(Assignment ass) {
        remove(ass);
        boolean ret = add(ass, -1);
        if (!ret)
            throw new IllegalStateException("Couldn't add " + ass);
    }

    /**
     * Directly moves the specified assignment to the specified slot (startNew)
     *
     * @return true if the specified assignment could be moved to startNew or not
     */
    public final boolean doMove(Assignment ass, int startNew) {
        // do not move if the same move was already done before
        // does not really improve performance nor efficiency
//        if (startNew >= 0) {
//            for (EventMove move : stack) {
//                if (move.ass == ass && move.startOld == startNew)
//                    return false;
//            }
//        }

        int startOld = ass.getStart();
        // remove from old slots
        List rollBackRmList = remove(ass);
        List rollbackAddList = internalAdd(ass, startNew);
        if (rollbackAddList == null) {
            rollbackRemove(ass, rollBackRmList, startOld);
            return false;
        }
        stack.add(new EventMove(ass, startOld, rollbackAddList, rollBackRmList));

        return true;
    }

    /**
     * This method removes the specified assignment from this period.
     * Either from the EventGroups if ass.start >= 0. otherwise from the
     * invalidAssignments.
     */
    List remove(Assignment ass) {
        int startOld = ass.getStart();
        List undoRemoveList = Collections.emptyList();
        if (startOld >= 0) {
            Event ev = ass.getEvent();
            int end = startOld + ev.getDuration();
            undoRemoveList = new ArrayList(end - startOld);
            for (int slot = startOld; slot < end; slot++) {
                Object obj = clusterArray.get(slot).remove(ass);
                if (obj == null)
                    throw new IllegalStateException("Cannot remove assignment:" + ass);
                undoRemoveList.add(obj);
            }
            conflictMatrix.remove(ass);
        } else
            invalidAssignments.remove(ass);

        ass.setStart(-1);
        return undoRemoveList;
    }

    /**
     * This method adds the specified assignment to the necessary EventGroups
     * if startTime >= 0. Otherwise it will assign the assignment to the
     * invalidAssignments
     */
    boolean add(Assignment ass, int startTime) {
        return internalAdd(ass, startTime) != null;
    }

    private List internalAdd(Assignment ass, int startTimeNew) {
        if (startTimeNew < 0) {
            if (invalidAssignments.contains(ass))
                throw new IllegalStateException(ass + " was already unassigned");

            ass.setStart(-1);
            invalidAssignments.add(ass);
            return Collections.emptyList();
        }

        int old = ass.getStart();
        if (old >= 0)
            throw new IllegalStateException(ass + " was unassigned; " + startTimeNew);

        ass.setStart(startTimeNew);
        Event ev = ass.getEvent();
        List matrixList = new ArrayList();
        Object oldMatrix;
        if (ev.getDuration() == 1) {
            oldMatrix = clusterArray.get(startTimeNew).assign(ass);
            if (oldMatrix == null) {
                ass.setStart(old);
                return null;
            }
            matrixList.add(oldMatrix);
        } else {
            int slot = startTimeNew;
            int end = startTimeNew + ass.getEvent().getDuration();
            Location loc = clusterArray.get(slot).calculateLocation(ass);
            if (loc == null) {
                ass.setStart(old);
                return null;
            }

            slot++;
            for (; slot < end; slot++) {
                if (!clusterArray.get(slot).isForcedAssignmentPossible(ass, loc)) {
                    ass.setStart(old);
                    return null;
                }
            }

            slot = startTimeNew;
            for (; slot < end; slot++) {
                oldMatrix = clusterArray.get(slot).forceAssignment(ass, loc);
                if (oldMatrix == null)
                    throw new IllegalStateException("Although it was calculated that"
                            + " adding event is possible it can't!!??: " + ass + " start:" + slot + " loc:" + loc);
                matrixList.add(oldMatrix);
            }
        }

        conflictMatrix.add(ass);
        return matrixList;
    }

    /**
     * This method reverts the moves already done. Used from injectAt().
     */
    final void rollback(int border) {
        EventMove move;
        //log.info("rollbackAssign " + (stack.size() - border) + " moves");
        while (stack.size() > border) {
            move = stack.removeLast();

            rollbackAssign(move.ass, move.rollbackAssignList);
            move.ass.setStart(move.startOld);
            rollbackRemove(move.ass, move.rollbackRmList, move.startOld);
        }
    }

    // TODO code duplication: very similar to add(Assignment)
    void rollbackRemove(Assignment ass, List undoData, int startOld) {
        if (startOld < 0) {
            invalidAssignments.add(ass);
        } else {
            Event ev = ass.getEvent();
            int end = startOld + ev.getDuration();
            int ii = 0;
            for (int slot = startOld; slot < end; slot++, ii++) {
                clusterArray.get(slot).rollbackRemove(ass, undoData.get(ii));
            }
            conflictMatrix.add(ass);
        }
    }

    // TODO code duplication: very similar to remove(Assignment)
    void rollbackAssign(Assignment ass, List undoData) {
        int slot = ass.getStart();
        if (slot < 0) {
            invalidAssignments.remove(ass);
        } else {
            int end = slot + ass.getEvent().getDuration();
            int ii = 0;
            for (; slot < end; slot++, ii++) {
                clusterArray.get(slot).rollbackAssign(ass, undoData.get(ii));
            }
            conflictMatrix.remove(ass);
        }
    }

    public void resetComplexity() {
        for (Assignment ass : allAssignments) {
            ass.setComplexity(0);
        }
    }

    /**
     * This method returns the biggest possible interval where we can place the
     * specified event in respect to all followers and beforers.
     *
     * @return null if no search interval is possible, i.e. order collision.
     *         Use the half open interval as follows: the 0-index as inclusive start
     *         and the 1-index as exclusive upper limit.
     */
    public int[] getSearchInterval(Assignment ass) {
        Integer maxEndTime = 0, minStartTime = noOfSlots;
        Set<Assignment> fl = followers.get(ass);
        Set<Assignment> bf = befores.get(ass);
        if (fl != null)
            for (Assignment flAss : fl) {
                if (flAss.getStart() >= 0 && flAss.getStart() < minStartTime)
                    minStartTime = flAss.getStart();
            }

        if (bf != null)
            for (Assignment bfAss : bf) {
                if (bfAss.getStart() >= 0 && bfAss.getStart() + bfAss.getEvent().getDuration() > maxEndTime)
                    maxEndTime = bfAss.getStart() + bfAss.getEvent().getDuration();
            }

        if (maxEndTime < minStartTime)
            return new int[]{maxEndTime, minStartTime};
        else
            return null;
    }

    private void out() {
        String str = "";
        int index = 0;
        for (AssignmentManager eg : clusterArray) {
            str += "[" + index + "," + eg.getAll().size() + "] ";
            Assignment obj[] = eg.getAll().toArray(new Assignment[0]);
            Arrays.sort(obj, new Comparator<Assignment>() {

                @Override
                public int compare(Assignment o1, Assignment o2) {
                    return ("" + o1.getEvent().getId()).compareTo("" + o2.getEvent().getId());
                }
            });
            for (Assignment ass : obj) {
                str += ass + ", ";
            }
            str += "\n";
            index++;
        }
        logger.info("" + str);
    }

    /**
     * @return a map which maps events to its soft constraint violations.
     */
    private Map<Event, Integer> getSoftConstraintMap() {
        Map<Event, Integer> map = FastMap.newInstance();

        // Initialize the number of persons that have a problem with the
        // specific Event - to avoid NPE's in Person.getSoftConstraintViolations
        for (Assignment ass : allAssignments) {
            map.put(ass.getEvent(), 0);
        }

        for (Entry<Person, Set<Assignment>> entry : allPersons.entrySet()) {
            TreeMap<Integer, Event> indexRaster = new TreeMap<Integer, Event>();
            for (Assignment ass : entry.getValue()) {
                if (ass.getStart() >= 0) {
                    assert ass.getStart() >= 0 : ass;
                    Event ret = indexRaster.put(ass.getStart(), ass.getEvent());
                    assert ret == null : "Should not overwrite event:" + ret + " at " + ass.getStart() + " with " + ass.getEvent();
                }
            }

            // The next call will change the problem map
            // according to the soft constraint violations
            PersonITCRasterConstraint prc = entry.getKey().getConstraint(PersonITCRasterConstraint.class);
            if (prc != null) {
                prc.getSoftConstraintViolations(indexRaster, map);
            }
        }

        return map;
    }

    public int getSoftConstraintsViolations() {
        return getSoftConstraintsViolations(false);
    }

    public int getSoftConstraintsViolations(boolean logResult) {
        int result = 0;
        for (Assignment ass : allAssignments) {
            for (Constraint constr : ass.getEvent().getConstraints()) {
                if (constr instanceof DifferentDayConstraint)
                    result += constr.getViolations(ass);
            }
        }

        if (logResult)
            logger.info("diff day:" + result);

        int result2 = 0;
        for (Entry<Person, Set<Assignment>> entry : allPersons.entrySet()) {
            TreeMap<Integer, Event> raster = new TreeMap<Integer, Event>();
            for (Assignment ass : entry.getValue()) {
                int startTime = ass.getStart();
                if (startTime >= 0)
                    raster.put(startTime, ass.getEvent());
            }

            for (Constraint constr : entry.getKey().getConstraints()) {
                if (constr instanceof MinGapsConstraint)
                    result2 += constr.getViolations();
                else if (constr instanceof PersonITCRasterConstraint)
                    result2 += constr.getViolations(raster);
            }
        }

        if (logResult)
            logger.info("min gaps:" + result2);

        return result + result2;
    }

    // used for test only
    void clearInvalid() {
        getInvalidAssignments().clear();
    }

    public Solution export() {
        Solution solution = new Solution();
        solution.setHardConflicts(getHardConstraintsViolations());
        solution.setSoftConflicts(getSoftConstraintsViolations());

        for (Assignment ass : allAssignments) {
            solution.addAssignment(new Assignment(ass));
        }

        return solution;
    }

    Collection<Assignment> getAll(int startTime, int duration) {
        Set<Assignment> set = FastSet.newInstance();
        int maximalIndex = Math.min(startTime + duration, clusterArray.size());
        for (int eventGroupIndex = startTime; eventGroupIndex < maximalIndex; eventGroupIndex++) {
            AssignmentManager eventGroup = clusterArray.get(eventGroupIndex);
            set.addAll(eventGroup.getAll());
        }

        return set;
    }

    String toString(Collection<Assignment> coll) {
        StringBuilder sb = new StringBuilder();
        for (Assignment ass : coll) {
            sb.append(ass);
            sb.append('\n');
        }
        return sb.toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        final Period other = (Period) obj;
        if (this.allAssignments != other.allAssignments
                && (this.allAssignments == null || !this.allAssignments.equals(other.allAssignments))) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return allAssignments.hashCode();
    }

    @Override
    public String toString() {
        int assigned = 0;
        for (Assignment ev : allAssignments) {
            if (ev.getStart() >= 0)
                assigned++;
        }
        return "Unassigned:" + getInvalidAssignments().size() + " Assigned:" + assigned;
    }
}

/**
* This class defines a move from a Event from startNew_ to startOld_
*/
class EventMove {

    int startOld;
    Assignment ass;
    List rollbackAssignList;
    List rollbackRmList;

    EventMove(Assignment ass_, int startOld_, List addList, List rmList) {
        ass = ass_;
        startOld = startOld_;
        this.rollbackAssignList = addList;
        this.rollbackRmList = rmList;
    }

    @Override
    public String toString() {
        return "old:" + startOld + "\t assignment:" + ass;
    }
}
TOP

Related Classes of de.timefinder.algo.ncp.Period

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.