/* * Copyright 2013 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.api.score.buildin.bendablelong; import java.util.Arrays; import org.optaplanner.core.api.score.AbstractBendableScore; import org.optaplanner.core.api.score.FeasibilityScore; import org.optaplanner.core.api.score.Score; import org.optaplanner.core.impl.score.buildin.bendablelong.BendableLongScoreDefinition; /** * This {@link Score} is based on n levels of long constraints. * The number of levels is bendable at configuration time. * <p> * This class is immutable. * <p> * The {@link #getHardLevelsSize()} and {@link #getSoftLevelsSize()} must be the same as in the * {@link BendableLongScoreDefinition} used. * @see Score */ public final class BendableLongScore extends AbstractBendableScore<BendableLongScore> implements FeasibilityScore<BendableLongScore> { /** * @param scoreString never null * @return never null */ public static BendableLongScore parseScore(String scoreString) { String[][] scoreTokens = parseBendableScoreTokens(BendableLongScore.class, scoreString); int initScore = parseInitScore(BendableLongScore.class, scoreString, scoreTokens[0][0]); long[] hardScores = new long[scoreTokens[1].length]; for (int i = 0; i < hardScores.length; i++) { hardScores[i] = parseLevelAsLong(BendableLongScore.class, scoreString, scoreTokens[1][i]); } long[] softScores = new long[scoreTokens[2].length]; for (int i = 0; i < softScores.length; i++) { softScores[i] = parseLevelAsLong(BendableLongScore.class, scoreString, scoreTokens[2][i]); } return valueOfUninitialized(initScore, hardScores, softScores); } /** * Creates a new {@link BendableLongScore}. * @param initScore see {@link Score#getInitScore()} * @param hardScores never null, never change that array afterwards: it must be immutable * @param softScores never null, never change that array afterwards: it must be immutable * @return never null */ public static BendableLongScore valueOfUninitialized(int initScore, long[] hardScores, long[] softScores) { return new BendableLongScore(initScore, hardScores, softScores); } /** * Creates a new {@link BendableLongScore}. * @param hardScores never null, never change that array afterwards: it must be immutable * @param softScores never null, never change that array afterwards: it must be immutable * @return never null */ public static BendableLongScore valueOf(long[] hardScores, long[] softScores) { return new BendableLongScore(0, hardScores, softScores); } public static BendableLongScore zero(int hardLevelsSize, int softLevelsSize) { return new BendableLongScore(0, new long[hardLevelsSize], new long[softLevelsSize]); } // ************************************************************************ // Fields // ************************************************************************ private final long[] hardScores; private final long[] softScores; /** * Private default constructor for default marshalling/unmarshalling of unknown frameworks that use reflection. * Such integration is always inferior to the specialized integration modules, such as * optaplanner-persistence-jpa, optaplanner-persistence-xstream, optaplanner-persistence-jaxb, ... */ @SuppressWarnings("unused") private BendableLongScore() { super(Integer.MIN_VALUE); hardScores = null; softScores = null; } /** * @param initScore see {@link Score#getInitScore()} * @param hardScores never null * @param softScores never null */ protected BendableLongScore(int initScore, long[] hardScores, long[] softScores) { super(initScore); this.hardScores = hardScores; this.softScores = softScores; } /** * @return not null, array copy because this class is immutable */ public long[] getHardScores() { return Arrays.copyOf(hardScores, hardScores.length); } /** * @return not null, array copy because this class is immutable */ public long[] getSoftScores() { return Arrays.copyOf(softScores, softScores.length); } @Override public int getHardLevelsSize() { return hardScores.length; } /** * @param index {@code 0 <= index <} {@link #getHardLevelsSize()} * @return higher is better */ public long getHardScore(int index) { return hardScores[index]; } @Override public int getSoftLevelsSize() { return softScores.length; } /** * @param index {@code 0 <= index <} {@link #getSoftLevelsSize()} * @return higher is better */ public long getSoftScore(int index) { return softScores[index]; } // ************************************************************************ // Worker methods // ************************************************************************ @Override public BendableLongScore toInitializedScore() { return initScore == 0 ? this : new BendableLongScore(0, hardScores, softScores); } @Override public BendableLongScore withInitScore(int newInitScore) { assertNoInitScore(); return new BendableLongScore(newInitScore, hardScores, softScores); } @Override public int getLevelsSize() { return hardScores.length + softScores.length; } /** * @param index {@code 0 <= index <} {@link #getLevelsSize()} * @return higher is better */ public long getHardOrSoftScore(int index) { if (index < hardScores.length) { return hardScores[index]; } else { return softScores[index - hardScores.length]; } } @Override public boolean isFeasible() { if (initScore < 0) { return false; } for (long hardScore : hardScores) { if (hardScore < 0) { return false; } } return true; } @Override public BendableLongScore add(BendableLongScore augment) { validateCompatible(augment); long[] newHardScores = new long[hardScores.length]; long[] newSoftScores = new long[softScores.length]; for (int i = 0; i < newHardScores.length; i++) { newHardScores[i] = hardScores[i] + augment.getHardScore(i); } for (int i = 0; i < newSoftScores.length; i++) { newSoftScores[i] = softScores[i] + augment.getSoftScore(i); } return new BendableLongScore( initScore + augment.getInitScore(), newHardScores, newSoftScores); } @Override public BendableLongScore subtract(BendableLongScore subtrahend) { validateCompatible(subtrahend); long[] newHardScores = new long[hardScores.length]; long[] newSoftScores = new long[softScores.length]; for (int i = 0; i < newHardScores.length; i++) { newHardScores[i] = hardScores[i] - subtrahend.getHardScore(i); } for (int i = 0; i < newSoftScores.length; i++) { newSoftScores[i] = softScores[i] - subtrahend.getSoftScore(i); } return new BendableLongScore( initScore - subtrahend.getInitScore(), newHardScores, newSoftScores); } @Override public BendableLongScore multiply(double multiplicand) { long[] newHardScores = new long[hardScores.length]; long[] newSoftScores = new long[softScores.length]; for (int i = 0; i < newHardScores.length; i++) { newHardScores[i] = (long) Math.floor(hardScores[i] * multiplicand); } for (int i = 0; i < newSoftScores.length; i++) { newSoftScores[i] = (long) Math.floor(softScores[i] * multiplicand); } return new BendableLongScore( (int) Math.floor(initScore * multiplicand), newHardScores, newSoftScores); } @Override public BendableLongScore divide(double divisor) { long[] newHardScores = new long[hardScores.length]; long[] newSoftScores = new long[softScores.length]; for (int i = 0; i < newHardScores.length; i++) { newHardScores[i] = (long) Math.floor(hardScores[i] / divisor); } for (int i = 0; i < newSoftScores.length; i++) { newSoftScores[i] = (long) Math.floor(softScores[i] / divisor); } return new BendableLongScore( (int) Math.floor(initScore / divisor), newHardScores, newSoftScores); } @Override public BendableLongScore power(double exponent) { long[] newHardScores = new long[hardScores.length]; long[] newSoftScores = new long[softScores.length]; for (int i = 0; i < newHardScores.length; i++) { newHardScores[i] = (long) Math.floor(Math.pow(hardScores[i], exponent)); } for (int i = 0; i < newSoftScores.length; i++) { newSoftScores[i] = (long) Math.floor(Math.pow(softScores[i], exponent)); } return new BendableLongScore( (int) Math.floor(Math.pow(initScore, exponent)), newHardScores, newSoftScores); } @Override public BendableLongScore negate() { long[] newHardScores = new long[hardScores.length]; long[] newSoftScores = new long[softScores.length]; for (int i = 0; i < newHardScores.length; i++) { newHardScores[i] = - hardScores[i]; } for (int i = 0; i < newSoftScores.length; i++) { newSoftScores[i] = - softScores[i]; } return new BendableLongScore(-initScore, newHardScores, newSoftScores); } @Override public Number[] toLevelNumbers() { Number[] levelNumbers = new Number[hardScores.length + softScores.length]; for (int i = 0; i < hardScores.length; i++) { levelNumbers[i] = hardScores[i]; } for (int i = 0; i < softScores.length; i++) { levelNumbers[hardScores.length + i] = softScores[i]; } return levelNumbers; } @Override public boolean equals(Object o) { // A direct implementation (instead of EqualsBuilder) to avoid dependencies if (this == o) { return true; } else if (o instanceof BendableLongScore) { BendableLongScore other = (BendableLongScore) o; if (getHardLevelsSize() != other.getHardLevelsSize() || getSoftLevelsSize() != other.getSoftLevelsSize()) { return false; } if (initScore != other.getInitScore()) { return false; } for (int i = 0; i < hardScores.length; i++) { if (hardScores[i] != other.getHardScore(i)) { return false; } } for (int i = 0; i < softScores.length; i++) { if (softScores[i] != other.getSoftScore(i)) { return false; } } return true; } else { return false; } } @Override public int hashCode() { // A direct implementation (instead of HashCodeBuilder) to avoid dependencies int hashCode = (17 * 37) + initScore; hashCode = (37 * hashCode) + Arrays.hashCode(hardScores); hashCode = (37 * hashCode) + Arrays.hashCode(softScores); return hashCode; } @Override public int compareTo(BendableLongScore other) { // A direct implementation (instead of CompareToBuilder) to avoid dependencies validateCompatible(other); if (initScore != other.getInitScore()) { return initScore < other.getInitScore() ? -1 : 1; } for (int i = 0; i < hardScores.length; i++) { if (hardScores[i] != other.getHardScore(i)) { return hardScores[i] < other.getHardScore(i) ? -1 : 1; } } for (int i = 0; i < softScores.length; i++) { if (softScores[i] != other.getSoftScore(i)) { return softScores[i] < other.getSoftScore(i) ? -1 : 1; } } return 0; } @Override public String toShortString() { return buildBendableShortString((n) -> ((Long) n).longValue() != 0L); } @Override public String toString() { StringBuilder s = new StringBuilder(((hardScores.length + softScores.length) * 4) + 13); s.append(getInitPrefix()); s.append("["); boolean first = true; for (long hardScore : hardScores) { if (first) { first = false; } else { s.append("/"); } s.append(hardScore); } s.append("]hard/["); first = true; for (long softScore : softScores) { if (first) { first = false; } else { s.append("/"); } s.append(softScore); } s.append("]soft"); return s.toString(); } public void validateCompatible(BendableLongScore other) { if (getHardLevelsSize() != other.getHardLevelsSize()) { throw new IllegalArgumentException("The score (" + this + ") with hardScoreSize (" + getHardLevelsSize() + ") is not compatible with the other score (" + other + ") with hardScoreSize (" + other.getHardLevelsSize() + ")."); } if (getSoftLevelsSize() != other.getSoftLevelsSize()) { throw new IllegalArgumentException("The score (" + this + ") with softScoreSize (" + getSoftLevelsSize() + ") is not compatible with the other score (" + other + ") with softScoreSize (" + other.getSoftLevelsSize() + ")."); } } @Override public boolean isCompatibleArithmeticArgument(Score otherScore) { if (!(otherScore instanceof BendableLongScore)) { return false; } BendableLongScore otherBendableScore = (BendableLongScore) otherScore; return hardScores.length == otherBendableScore.hardScores.length && softScores.length == otherBendableScore.softScores.length; } }