/*
* Copyright 2014 DataGenerator Contributors
*
* 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.finra.datagenerator.engine.scxml.tags.boundary;
import org.finra.datagenerator.consumer.EquivalenceClassTransformer;
import org.finra.datagenerator.engine.scxml.tags.CustomTagExtension;
import org.finra.datagenerator.engine.scxml.tags.boundary.action.BoundaryActionDecimal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @param <T> a generic type that represents a BoundaryTag
*/
public abstract class BoundaryDecimal<T extends BoundaryActionDecimal> implements CustomTagExtension<T> {
/**
* @param action an Action of the type handled by this class
* @param positive a boolean denoting positive or negative cases
* @return a list of Strings that contain the states
*/
public List<String> setParameters(T action, boolean positive) {
BigDecimal max, min;
int minLen;
int maxLen;
int[] lengths = new int[2];
boolean isNullable = true;
String nullable = action.getNullable();
if (!nullable.equalsIgnoreCase("true")) {
isNullable = false;
}
if (action.getLength() == null) {
lengths[0] = Integer.parseInt("10");
lengths[1] = Integer.parseInt("0");
} else {
if (action.getLength().contains(",")) {
lengths[0] = Integer.parseInt(action.getLength().split(",")[0]);
lengths[1] = Integer.parseInt(action.getLength().split(",")[1]);
} else {
lengths[0] = Integer.parseInt(action.getLength());
if (Integer.parseInt(action.getLength()) > 38) {
lengths[0] = Integer.parseInt("38");
}
lengths[1] = Integer.parseInt("0");
}
}
if (action.getMinLen() != null) {
if (Integer.parseInt(action.getMinLen()) <= lengths[0]) {
minLen = Integer.parseInt(action.getMinLen());
} else {
minLen = lengths[0];
}
} else {
minLen = -1;
}
if (action.getMaxLen() != null) {
if (Integer.parseInt(action.getMaxLen()) <= lengths[0]) {
maxLen = Integer.parseInt(action.getMaxLen());
} else {
maxLen = lengths[0];
}
} else {
maxLen = -1;
}
int exp = lengths[0];
if (action.getMin() != null) {
min = new BigDecimal(action.getMin());
} else {
min = BigDecimal.TEN.pow(exp);
min = min.negate();
min = min.add(BigDecimal.ONE);
}
if (action.getMax() != null) {
max = new BigDecimal(action.getMax());
} else {
max = BigDecimal.TEN.pow(exp);
max = max.subtract(BigDecimal.ONE);
}
if (positive) {
return positive(isNullable, min, max, minLen, maxLen, lengths);
} else {
return negative(isNullable, min, max, minLen, maxLen, lengths);
}
}
/**
* @param nullable nullable
* @param min minimum value
* @param max maximum value
* @param minLen minimum length
* @param maxLen maximum length
* @param lengths number of leading and trailing digits
* @return a list of Strings that contain the boundary cases
*/
public List<String> negative(boolean nullable, BigDecimal min, BigDecimal max,
int minLen, int maxLen, int[] lengths) {
boolean minMaxLenPresent = true;
boolean minMaxPresent = true;
EquivalenceClassTransformer eq = new EquivalenceClassTransformer();
List<String> values = new LinkedList<>();
Method m;
StringBuilder decimalUpperBound = new StringBuilder();
StringBuilder decimalLowerBound = new StringBuilder();
try {
m = EquivalenceClassTransformer.class.getDeclaredMethod("digitSequence", StringBuilder.class, int.class);
m.setAccessible(true);
if (minLen == -1 && maxLen == -1) {
minMaxLenPresent = false;
}
if (minLen == -1) {
minLen = min.toString().length();
}
if (maxLen == -1) {
maxLen = lengths[0];
}
int exp = lengths[0];
BigDecimal defaultMin = BigDecimal.TEN.pow(exp);
defaultMin = defaultMin.negate();
defaultMin = defaultMin.add(BigDecimal.ONE);
BigDecimal defaultMax = BigDecimal.TEN.pow(exp);
defaultMax = defaultMax.subtract(BigDecimal.ONE);
if (min.compareTo(defaultMin) == 0 && max.compareTo(defaultMax) == 0) {
minMaxPresent = false;
}
if (minMaxLenPresent && !minMaxPresent) {
m.invoke(eq, decimalLowerBound, minLen - 1);
m.invoke(eq, decimalUpperBound, maxLen + 1);
} else {
decimalLowerBound.append(constructData(min.subtract(BigDecimal.ONE), minLen - 1, maxLen + 1, lengths, false));
decimalUpperBound.append(constructData(max.add(BigDecimal.ONE), minLen - 1, maxLen + 1, lengths, true));
}
values.add(decimalLowerBound.toString());
values.add(decimalUpperBound.toString());
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
if (!nullable) {
values.add("");
}
return values;
}
/**
* @param nullable nullable
* @param min minimum value
* @param max maximum value
* @param minLen minimum length
* @param maxLen maximum length
* @param lengths number of leading and trailing digits
* @return a list of Strings that contain the boundary cases
*/
public List<String> positive(boolean nullable, BigDecimal min, BigDecimal max,
int minLen, int maxLen, int[] lengths) {
boolean minMaxLenPresent = true;
boolean minMaxPresent = true;
EquivalenceClassTransformer eq = new EquivalenceClassTransformer();
List<String> values = new LinkedList<>();
Method m;
StringBuilder decimalUpperBound = new StringBuilder();
StringBuilder decimalLowerBound = new StringBuilder();
StringBuilder decimalMid = new StringBuilder();
BigDecimal mid;
try {
m = EquivalenceClassTransformer.class.getDeclaredMethod("digitSequence", StringBuilder.class, int.class);
m.setAccessible(true);
if (minLen == -1 && maxLen == -1) {
minMaxLenPresent = false;
}
if (minLen == -1) {
minLen = min.toString().length();
if (Character.toString(min.toString().charAt(0)).equals("-")) {
minLen = min.toString().length() - 1;
}
if (maxLen != -1) {
minMaxLenPresent = false;
}
}
if (maxLen == -1) {
maxLen = lengths[0];
if (minLen != -1) {
minMaxLenPresent = false;
}
}
int exp = lengths[0];
BigDecimal defaultMin = BigDecimal.TEN.pow(exp);
defaultMin = defaultMin.negate();
defaultMin = defaultMin.add(BigDecimal.ONE);
BigDecimal defaultMax = BigDecimal.TEN.pow(exp);
defaultMax = defaultMax.subtract(BigDecimal.ONE);
if (min.compareTo(defaultMin) == 0 && max.compareTo(defaultMax) == 0) {
minMaxPresent = false;
}
if (minMaxLenPresent && !minMaxPresent) {
m.invoke(eq, decimalLowerBound, minLen);
m.invoke(eq, decimalUpperBound, maxLen);
m.invoke(eq, decimalMid, (maxLen - minLen) / 2 + minLen);
} else {
mid = max.subtract(min);
mid = mid.divide(new BigDecimal("2"));
mid = mid.add(min);
decimalLowerBound.append(constructData(min, minLen, maxLen, lengths, false));
decimalUpperBound.append(constructData(max, minLen, maxLen, lengths, true));
decimalMid.append(constructData(mid, minLen, maxLen, lengths, false));
if (min.compareTo(BigDecimal.ZERO) == -1 && max.compareTo(BigDecimal.ZERO) == 1
&& !values.contains("0")) {
values.add("0");
}
}
values.add(decimalLowerBound.toString());
values.add(decimalUpperBound.toString());
values.add(decimalMid.toString());
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
if (nullable) {
values.add("");
}
return values;
}
/**
* @param boundary the min or max value
* @param minLen the min Length
* @param maxLen the max Length
* @param lengths the length parameters for decimal field
* @param upperBound boolean for upperBound bound
* @return the number of trailing digits to append to the value
*/
public String constructData(BigDecimal boundary, int minLen, int maxLen, int[] lengths, boolean upperBound) {
Method m;
EquivalenceClassTransformer eq = new EquivalenceClassTransformer();
StringBuilder value = new StringBuilder();
int trailing;
try {
m = EquivalenceClassTransformer.class.getDeclaredMethod("digitSequence", StringBuilder.class, int.class);
m.setAccessible(true);
if (upperBound) {
if (boundary.toString().length() > maxLen) {
value.append(boundary.toString().substring(0, maxLen - 1));
return value.toString();
}
if (boundary.toString().length() + lengths[1] <= maxLen) {
trailing = lengths[1];
} else {
if (maxLen - boundary.toString().length() <= lengths[1]) {
trailing = maxLen - boundary.toString().length();
} else {
trailing = lengths[1] - boundary.toString().length();
}
}
if (trailing > 0) {
value.append(boundary.subtract(BigDecimal.ONE));
} else {
value.append(boundary);
}
} else {
if (boundary.toString().length() > minLen) {
if (Character.toString(boundary.toString().charAt(0)).equals("-")) {
value.append(boundary.toString().substring(0, minLen + 1));
} else {
value.append(boundary.toString().substring(0, minLen));
}
return value.toString();
}
if (boundary.toString().length() + lengths[1] <= minLen) {
trailing = lengths[1];
} else {
if (minLen - boundary.toString().length() <= lengths[1]) {
trailing = minLen - boundary.toString().length();
} else {
trailing = lengths[1] - boundary.toString().length();
}
}
value.append(boundary);
}
if (trailing > 0) {
value.append(".");
}
m.invoke(eq, value, trailing);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return value.toString();
}
/**
* @param action an Action of the type handled by this class
* @param possibleStateList a current list of possible states produced so far from
* expanding a model state
* @param variableValue a list storing the values
* @return a list of Maps containing the cross product of all states
*/
public List<Map<String, String>> returnStates(T action,
List<Map<String, String>> possibleStateList, List<String> variableValue) {
List<Map<String, String>> states = new LinkedList<>();
for (Map<String, String> p : possibleStateList) {
for (String s : variableValue) {
HashMap<String, String> n = new HashMap<>(p);
n.put(action.getName(), s);
states.add(n);
}
}
return states;
}
}