/*
* 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.api.score;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.function.Predicate;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
/**
* Abstract superclass for {@link Score}.
* <p>
* Subclasses must be immutable.
* @param <S> the actual score type
* @see Score
* @see HardSoftScore
*/
public abstract class AbstractScore<S extends Score> implements Score<S>, Serializable {
protected static final String INIT_LABEL = "init";
protected static String[] parseScoreTokens(Class<? extends Score> scoreClass,
String scoreString, String... levelSuffixes) {
String[] scoreTokens = new String[levelSuffixes.length + 1];
String[] suffixedScoreTokens = scoreString.split("/");
int startIndex;
if (suffixedScoreTokens.length == levelSuffixes.length + 1) {
String suffixedScoreToken = suffixedScoreTokens[0];
if (!suffixedScoreToken.endsWith(INIT_LABEL)) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(false, levelSuffixes) + "):"
+ " the suffixedScoreToken (" + suffixedScoreToken
+ ") does not end with levelSuffix (" + INIT_LABEL + ").");
}
scoreTokens[0] = suffixedScoreToken.substring(0, suffixedScoreToken.length() - INIT_LABEL.length());
startIndex = 1;
} else if (suffixedScoreTokens.length == levelSuffixes.length) {
scoreTokens[0] = "0";
startIndex = 0;
} else {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(false, levelSuffixes) + "):"
+ " the suffixedScoreTokens length (" + suffixedScoreTokens.length
+ ") differs from the levelSuffixes length ("
+ levelSuffixes.length + " or " + (levelSuffixes.length + 1) + ").");
}
for (int i = 0; i < levelSuffixes.length; i++) {
String suffixedScoreToken = suffixedScoreTokens[startIndex + i];
String levelSuffix = levelSuffixes[i];
if (!suffixedScoreToken.endsWith(levelSuffix)) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(false, levelSuffixes) + "):"
+ " the suffixedScoreToken (" + suffixedScoreToken
+ ") does not end with levelSuffix (" + levelSuffix + ").");
}
scoreTokens[1 + i] = suffixedScoreToken.substring(0, suffixedScoreToken.length() - levelSuffix.length());
}
return scoreTokens;
}
protected static int parseInitScore(Class<? extends Score> scoreClass,
String scoreString, String initScoreString) {
try {
return Integer.parseInt(initScoreString);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName() + ") has a initScoreString ("
+ initScoreString + ") which is not a valid integer.", e);
}
}
protected static int parseLevelAsInt(Class<? extends Score> scoreClass,
String scoreString, String levelString) {
try {
return Integer.parseInt(levelString);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName() + ") has a levelString (" + levelString
+ ") which is not a valid integer.", e);
}
}
protected static long parseLevelAsLong(Class<? extends Score> scoreClass,
String scoreString, String levelString) {
try {
return Long.parseLong(levelString);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName() + ") has a levelString (" + levelString
+ ") which is not a valid long.", e);
}
}
protected static double parseLevelAsDouble(Class<? extends Score> scoreClass,
String scoreString, String levelString) {
try {
return Double.parseDouble(levelString);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName() + ") has a levelString (" + levelString
+ ") which is not a valid double.", e);
}
}
protected static BigDecimal parseLevelAsBigDecimal(Class<? extends Score> scoreClass,
String scoreString, String levelString) {
try {
return new BigDecimal(levelString);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName() + ") has a levelString (" + levelString
+ ") which is not a valid BigDecimal.", e);
}
}
protected static String buildScorePattern(boolean bendable, String... levelSuffixes) {
StringBuilder scorePattern = new StringBuilder(levelSuffixes.length * 10);
boolean first = true;
for (String levelSuffix : levelSuffixes) {
if (first) {
first = false;
} else {
scorePattern.append("/");
}
if (bendable) {
scorePattern.append("[999/.../999]");
} else {
scorePattern.append("999");
}
scorePattern.append(levelSuffix);
}
return scorePattern.toString();
}
// ************************************************************************
// Fields
// ************************************************************************
protected final int initScore;
/**
* @param initScore see {@link Score#getInitScore()}
*/
protected AbstractScore(int initScore) {
this.initScore = initScore;
// The initScore can be positive during statistical calculations.
}
@Override
public int getInitScore() {
return initScore;
}
// ************************************************************************
// Worker methods
// ************************************************************************
@Override
public boolean isSolutionInitialized() {
return initScore >= 0;
}
protected void assertNoInitScore() {
if (initScore != 0) {
throw new IllegalStateException("The score (" + this + ")'s initScore (" + initScore
+ ") should be 0.\n"
+ "Maybe the score calculator is calculating the initScore too,"
+ " although it's the score director's responsibility.");
}
}
protected String getInitPrefix() {
if (initScore == 0) {
return "";
}
return initScore + INIT_LABEL + "/";
}
protected String buildShortString(Predicate<Number> notZero, String... levelLabels) {
StringBuilder shortString = new StringBuilder();
if (initScore != 0) {
shortString.append(initScore).append(INIT_LABEL);
}
int i = 0;
for (Number levelNumber : toLevelNumbers()) {
if (notZero.test(levelNumber)) {
if (shortString.length() > 0) {
shortString.append("/");
}
shortString.append(levelNumber).append(levelLabels[i]);
}
i++;
}
if (shortString.length() == 0) {
// Even for BigDecimals we use "0" over "0.0" because different levels can have different scales
return "0";
}
return shortString.toString();
}
}