/*
* Copyright 2010 Red Hat, Inc. and/or its affiliates.
*
* 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 org.optaplanner.core.impl.heuristic.move;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.impl.score.director.ScoreDirector;
/**
* A CompositeMove is composed out of multiple other moves.
* <p>
* Warning: each of moves in the moveList must not rely on the effect of a previous move in the moveList
* to create its undoMove correctly.
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
* @see Move
*/
public class CompositeMove<Solution_> implements Move<Solution_> {
/**
* @param moves never null, sometimes empty. Do not modify this argument afterwards or the CompositeMove corrupts.
* @return never null
*/
@SafeVarargs
public static <Solution_, Move_ extends Move<Solution_>> Move<Solution_> buildMove(Move_... moves) {
int size = moves.length;
if (size > 1) {
return new CompositeMove<>(moves);
} else if (size == 1) {
return moves[0];
} else {
return new NoChangeMove<>();
}
}
/**
* @param moveList never null, sometimes empty
* @return never null
*/
public static <Solution_, Move_ extends Move<Solution_>> Move<Solution_> buildMove(List<Move_> moveList) {
int size = moveList.size();
if (size > 1) {
return new CompositeMove<>(moveList.toArray(new Move[0]));
} else if (size == 1) {
return moveList.get(0);
} else {
return new NoChangeMove<>();
}
}
// ************************************************************************
// Non-static members
// ************************************************************************
protected final Move<Solution_>[] moves;
/**
* @param moves never null, never empty. Do not modify this argument afterwards or this CompositeMove corrupts.
*/
@SafeVarargs
public CompositeMove(Move<Solution_>... moves) {
this.moves = moves;
}
public Move<Solution_>[] getMoves() {
return moves;
}
@Override
public boolean isMoveDoable(ScoreDirector<Solution_> scoreDirector) {
for (Move<Solution_> move : moves) {
if (!move.isMoveDoable(scoreDirector)) {
return false;
}
}
return true;
}
@Override
public CompositeMove<Solution_> doMove(ScoreDirector<Solution_> scoreDirector) {
Move<Solution_>[] undoMoves = new Move[moves.length];
for (int i = 0; i < moves.length; i++) {
// Calls scoreDirector.triggerVariableListeners() between moves
// because a later move can depend on the shadow variables changed by an earlier move
Move<Solution_> undoMove = moves[i].doMove(scoreDirector);
// Undo in reverse order and each undoMove is created after previous moves have been done
undoMoves[moves.length - 1 - i] = undoMove;
}
// No need to call scoreDirector.triggerVariableListeners() because Move.doMove() already does it for every move.
return new CompositeMove<>(undoMoves);
}
// ************************************************************************
// Introspection methods
// ************************************************************************
@Override
public String getSimpleMoveTypeDescription() {
Set<String> childMoveTypeDescriptionSet = new TreeSet<>();
for (Move<Solution_> move : moves) {
childMoveTypeDescriptionSet.add(move.getSimpleMoveTypeDescription());
}
StringBuilder moveTypeDescription = new StringBuilder(20 * (moves.length + 1));
moveTypeDescription.append(getClass().getSimpleName()).append("(");
String delimiter = "";
for (String childMoveTypeDescription : childMoveTypeDescriptionSet) {
moveTypeDescription.append(delimiter).append("* ").append(childMoveTypeDescription);
delimiter = ", ";
}
moveTypeDescription.append(")");
return moveTypeDescription.toString();
}
@Override
public Collection<? extends Object> getPlanningEntities() {
Set<Object> entities = new LinkedHashSet<>(moves.length * 2);
for (Move<Solution_> move : moves) {
entities.addAll(move.getPlanningEntities());
}
return entities;
}
@Override
public Collection<? extends Object> getPlanningValues() {
Set<Object> values = new LinkedHashSet<>(moves.length * 2);
for (Move<Solution_> move : moves) {
values.addAll(move.getPlanningValues());
}
return values;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o instanceof CompositeMove) {
CompositeMove<?> other = (CompositeMove) o;
return Arrays.equals(moves, other.moves);
} else {
return false;
}
}
@Override
public int hashCode() {
return Arrays.hashCode(moves);
}
@Override
public String toString() {
return Arrays.toString(moves);
}
}