/*******************************************************************************
* Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v3
* which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
******************************************************************************/
package com.opendoorlogistics.core.formulae.definitions;
import java.awt.Color;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.opendoorlogistics.core.distances.functions.FmDistance;
import com.opendoorlogistics.core.distances.functions.FmDrivingDistance;
import com.opendoorlogistics.core.distances.functions.FmDrivingRouteGeom;
import com.opendoorlogistics.core.distances.functions.FmDrivingRouteGeom.FmDrivingRouteGeomUncached;
import com.opendoorlogistics.core.distances.functions.FmDrivingRouteGeomFromLine;
import com.opendoorlogistics.core.distances.functions.FmDrivingTime;
import com.opendoorlogistics.core.distances.functions.FmDrivingTime.FmDrivingTimeUncached;
import com.opendoorlogistics.core.formulae.Function;
import com.opendoorlogistics.core.formulae.FunctionFactory;
import com.opendoorlogistics.core.formulae.FunctionUtils;
import com.opendoorlogistics.core.formulae.Functions.FmAbs;
import com.opendoorlogistics.core.formulae.Functions.FmAcos;
import com.opendoorlogistics.core.formulae.Functions.FmAnd;
import com.opendoorlogistics.core.formulae.Functions.FmAppProperty;
import com.opendoorlogistics.core.formulae.Functions.FmAsin;
import com.opendoorlogistics.core.formulae.Functions.FmAtan;
import com.opendoorlogistics.core.formulae.Functions.FmBitwiseOr;
import com.opendoorlogistics.core.formulae.Functions.FmCeil;
import com.opendoorlogistics.core.formulae.Functions.FmColour;
import com.opendoorlogistics.core.formulae.Functions.FmColourImage;
import com.opendoorlogistics.core.formulae.Functions.FmColourMultiply;
import com.opendoorlogistics.core.formulae.Functions.FmConcatenate;
import com.opendoorlogistics.core.formulae.Functions.FmConst;
import com.opendoorlogistics.core.formulae.Functions.FmContains;
import com.opendoorlogistics.core.formulae.Functions.FmCos;
import com.opendoorlogistics.core.formulae.Functions.FmCreateUUID;
import com.opendoorlogistics.core.formulae.Functions.FmDarken;
import com.opendoorlogistics.core.formulae.Functions.FmDecimalFormat;
import com.opendoorlogistics.core.formulae.Functions.FmDecimalHours;
import com.opendoorlogistics.core.formulae.Functions.FmDivide;
import com.opendoorlogistics.core.formulae.Functions.FmEquals;
import com.opendoorlogistics.core.formulae.Functions.FmFadeImage;
import com.opendoorlogistics.core.formulae.Functions.FmFirstNonNull;
import com.opendoorlogistics.core.formulae.Functions.FmFloor;
import com.opendoorlogistics.core.formulae.Functions.FmGreaterThan;
import com.opendoorlogistics.core.formulae.Functions.FmGreaterThanEqualTo;
import com.opendoorlogistics.core.formulae.Functions.FmGreyscale;
import com.opendoorlogistics.core.formulae.Functions.FmIfThenElse;
import com.opendoorlogistics.core.formulae.Functions.FmIndexOf;
import com.opendoorlogistics.core.formulae.Functions.FmLeft;
import com.opendoorlogistics.core.formulae.Functions.FmLen;
import com.opendoorlogistics.core.formulae.Functions.FmLerp;
import com.opendoorlogistics.core.formulae.Functions.FmLessThan;
import com.opendoorlogistics.core.formulae.Functions.FmLessThanEqualTo;
import com.opendoorlogistics.core.formulae.Functions.FmLighten;
import com.opendoorlogistics.core.formulae.Functions.FmLineStringEnd;
import com.opendoorlogistics.core.formulae.Functions.FmLineStringFraction;
import com.opendoorlogistics.core.formulae.Functions.FmLn;
import com.opendoorlogistics.core.formulae.Functions.FmLog10;
import com.opendoorlogistics.core.formulae.Functions.FmLower;
import com.opendoorlogistics.core.formulae.Functions.FmMax;
import com.opendoorlogistics.core.formulae.Functions.FmMin;
import com.opendoorlogistics.core.formulae.Functions.FmMod;
import com.opendoorlogistics.core.formulae.Functions.FmMultiply;
import com.opendoorlogistics.core.formulae.Functions.FmNotEqual;
import com.opendoorlogistics.core.formulae.Functions.FmOr;
import com.opendoorlogistics.core.formulae.Functions.FmPostcodeUKFormatUnit;
import com.opendoorlogistics.core.formulae.Functions.FmPostcodeUk;
import com.opendoorlogistics.core.formulae.Functions.FmPow;
import com.opendoorlogistics.core.formulae.Functions.FmRand;
import com.opendoorlogistics.core.formulae.Functions.FmRandColour;
import com.opendoorlogistics.core.formulae.Functions.FmRandData;
import com.opendoorlogistics.core.formulae.Functions.FmTileFactory;
import com.opendoorlogistics.core.formulae.Functions.FmRandData.RandDataType;
import com.opendoorlogistics.core.formulae.Functions.FmRandPalletColour;
import com.opendoorlogistics.core.formulae.Functions.FmRandomSymbol;
import com.opendoorlogistics.core.formulae.Functions.FmRegExpMatchedGroup;
import com.opendoorlogistics.core.formulae.Functions.FmRegExpMatches;
import com.opendoorlogistics.core.formulae.Functions.FmReplace;
import com.opendoorlogistics.core.formulae.Functions.FmRound;
import com.opendoorlogistics.core.formulae.Functions.FmRound2Second;
import com.opendoorlogistics.core.formulae.Functions.FmSin;
import com.opendoorlogistics.core.formulae.Functions.FmSqrt;
import com.opendoorlogistics.core.formulae.Functions.FmStringDateTimeStamp;
import com.opendoorlogistics.core.formulae.Functions.FmStringFormat;
import com.opendoorlogistics.core.formulae.Functions.FmSubtract;
import com.opendoorlogistics.core.formulae.Functions.FmSum;
import com.opendoorlogistics.core.formulae.Functions.FmSwitch;
import com.opendoorlogistics.core.formulae.Functions.FmTan;
import com.opendoorlogistics.core.formulae.Functions.FmTemperatureColours;
import com.opendoorlogistics.core.formulae.Functions.FmTime;
import com.opendoorlogistics.core.formulae.Functions.FmUpper;
import com.opendoorlogistics.core.formulae.StringTokeniser;
import com.opendoorlogistics.core.formulae.definitions.FunctionDefinition.ArgumentType;
import com.opendoorlogistics.core.formulae.definitions.FunctionDefinition.FunctionArgument;
import com.opendoorlogistics.core.formulae.definitions.FunctionDefinition.FunctionType;
import com.opendoorlogistics.core.geometry.functions.FmCentroid;
import com.opendoorlogistics.core.geometry.functions.FmGeom;
import com.opendoorlogistics.core.geometry.functions.FmGeom.GeomType;
import com.opendoorlogistics.core.geometry.functions.FmGeomArea;
import com.opendoorlogistics.core.geometry.functions.FmGeomBorder;
import com.opendoorlogistics.core.geometry.functions.FmGeomContains;
import com.opendoorlogistics.core.geometry.functions.FmLatitude;
import com.opendoorlogistics.core.geometry.functions.FmLongitude;
import com.opendoorlogistics.core.geometry.functions.FmShapefileLookup;
import com.opendoorlogistics.core.gis.map.data.UserRenderFlags;
import com.opendoorlogistics.core.gis.postcodes.UKPostcodes.UKPostcodeLevel;
import com.opendoorlogistics.core.scripts.TableReference;
import com.opendoorlogistics.core.scripts.execution.ExecutionReportImpl;
import com.opendoorlogistics.core.utils.Colours;
import com.opendoorlogistics.core.utils.strings.StandardisedStringTreeMap;
import com.opendoorlogistics.core.utils.strings.Strings;
public final class FunctionDefinitionLibrary {
public static FunctionDefinitionLibrary DEFAULT_LIB = new FunctionDefinitionLibrary().buildStd();
private StandardisedStringTreeMap<List<FunctionDefinition>> map = new StandardisedStringTreeMap<>(false);
private int nextOperatorPrecedence = 0;
public void add(FunctionDefinition definition) {
List<FunctionDefinition> list = map.get(definition.getName());
if (list == null) {
list = new ArrayList<>();
map.put(definition.getName(), list);
} else if (definition.getType() == FunctionType.OPERATOR) {
throw new RuntimeException("Cannot overload operators");
}
list.add(definition);
// keep sorted so method with smallest number of arguments is shown first
Collections.sort(list, new Comparator<FunctionDefinition>() {
@Override
public int compare(FunctionDefinition o1, FunctionDefinition o2) {
return Integer.compare(o1.nbArgs(), o2.nbArgs());
}
});
}
public FunctionDefinitionLibrary(){}
public FunctionDefinitionLibrary(FunctionDefinitionLibrary copyFromThis){
for(Map.Entry<String,List< FunctionDefinition>> entry:copyFromThis.map.entrySet()){
map.put(entry.getKey(), entry.getValue());
}
nextOperatorPrecedence = copyFromThis.nextOperatorPrecedence;
}
public FunctionDefinitionLibrary buildStd() {
// add operators IN ORDER OF PRECEDENCE
addVargsOperator(FmMultiply.class, "*", "Multiply values together.");
addStandardOperator(FmDivide.class, "/", "Divide one value by the other.");
addStandardOperator(FmMod.class, "%", "Remainder of dividing first value by second.");
addVargsOperator(FmSum.class , "+","Add values together.");
addVargsOperator(FmConcatenate.class, "&", "Concatenate strings.");
for(String s:StringTokeniser.minuses()){
addStandardOperator(FmSubtract.class, s, "Subtract second value from first.");
}
addStandardOperator(FmLessThanEqualTo.class, "<=", "Test if first value is less than or equal to second.");
addStandardOperator(FmGreaterThanEqualTo.class, ">=", "Test if first value is greater than or equal to second.");
addStandardOperator(FmLessThan.class, "<", "Test if first value is less than second.");
addStandardOperator(FmGreaterThan.class, ">", "Test if first value is greater than second.");
addStandardOperator(FmEquals.class, "==" ,"Test if first value is equal to second." );
addStandardOperator(FmEquals.class, "=","Test if first value is equal to second." );
addStandardOperator(FmNotEqual.class, "!=" ,"Test if first value is not equal to second." );
addStandardOperator(FmNotEqual.class, "<>","Test if first value is not equal to second" );
addStandardOperator(FmBitwiseOr.class, "|", "Bitwise or");
addStandardOperator(FmAnd.class, "&&", "Boolean and function.");
addStandardOperator(FmOr.class, "||", "Boolean or function.");
// functions
addStandardFunction(FmIfThenElse.class, "if", "Test the first value and if this is true return the second value, otherwise return the third.","condition", "value_if_true", "value_if_false");
addStandardFunction(FmRand.class, "rand", "Random number between 0 and 1.");
addStandardFunction(FmRandomSymbol.class, "randsymbol", "Return a randomly chosen symbol name.");
addStandardFunction(FmAcos.class, "acos","Inverse cosine function", "value");
addStandardFunction(FmAsin.class, "asin","Inverse sine function", "value");
addStandardFunction(FmAtan.class, "atan","Inverse tan function", "value");
addStandardFunction(FmCos.class, "cos", "Cosine function","value");
addStandardFunction(FmLn.class, "ln","Natural logarithm", "value");
addStandardFunction(FmLog10.class, "log10","Logarithm to base to", "value");
addStandardFunction(FmSqrt.class, "sqrt","Calculate the square root of the value", "value");
addStandardFunction(FmSin.class, "sin","Sine function", "value");
addStandardFunction(FmTan.class, "tan","Tan function", "value");
addStandardFunction(FmAbs.class, "abs", "Get the absolute of the value","value");
addStandardFunction(FmCeil.class, "ceil","", "value");
addStandardFunction(FmFloor.class, "floor","Return the integer part of the number, e.g. 2.3 returns 2.", "value");
addStandardFunction(FmPow.class, "pow","Return the value of the 1st number raised to the power of the 2nd number.", "value1","value2");
addStandardFunction(FmRound.class, "round","Round to the nearest integer value.", "value");
for(String constName : new String[]{"const", "c"}){
addStandardFunction(FmConst.class, constName,"Create a constant string value from the input value. This is used to distinguish between string constants and source columns. For example, if your data adapter's source table has a column called \"name\" and you want to create a string constant also containing \"name\", you should use const(\"name\") as \"name\" on its own will automatically be converted to a source column reference.", "value");
}
for(String spelling : new String[]{"colour", "color"}){
addStandardFunction(FmColour.class, spelling,"Create a colour object from red, green and blue values in the range 0 to 1.", "red", "green", "blue");
addStandardFunction(FmColour.class, spelling,"Create a colour object from red, green, blue and alpha (transparency) values in the range 0 to 1.", "red", "green", "blue", "alpha");
addStandardFunction(FmColourImage.class,spelling + "image", "Apply the colour filter to the image. All non-transparent pixels are linearly interpolated (lerped) between their original colour and the input colour, according to the lerp fraction.", "image", "colour", "lerpFraction");
}
addStandardFunction(FmAppProperty.class, "property", "Retrieve an application property from the application's properties file. Property is returned as a string.", "propertykey");
addStandardFunction(FmFadeImage.class, "fadeimage", "Fade the image, making it transparent by multiplying its alpha channel by the fade value.", "image", "fadevalue");
addStandardFunction(FmRandColour.class, "randcolor", "Create a random colour based on the input value.","seed_value");
addStandardFunction(FmRandColour.class, "randcolour", "Create a random colour based on the input value.","seed_value");
addStandardFunction(FmColourMultiply.class, "colourmultiply", "Multiply the input colour by the factor, making it lighter or darker.","colour", "factor");
addStandardFunction(FmColourMultiply.class, "colormultiply", "Multiply the input color by the factor, making it lighter or darker.","color", "factor");
addStandardFunction(FmGreyscale.class, "greyscale", "Convert the input colour to grey, based on the factor (between 0 and 1).", "colour", "factor");
addStandardFunction(FmGreyscale.class, "grayscale", "Convert the input color to gray, based on the factor (between 0 and 1).", "color", "factor");
addStandardFunction(FmLighten.class, "lighten", "Lighten the input colour based on the factor (between 0 and 1).", "colour", "factor");
addStandardFunction(FmDarken.class, "darken", "Darken the input colour based on the factor (between 0 and 1).", "colour", "factor");
addStandardFunction(FmRandPalletColour.class, "randPalletColour", "Choose a random colour from ODL's internal pallet.");
addStandardFunction(FmRandPalletColour.class, "randPalletColor", "Choose a random color from ODL's internal pallet.");
addStandardFunction(FmLerp.class, "lerp", "Linearly interpolate between value a and value b based on value c (which is in the range 0 to 1).", "a","b","c");
addStandardFunction(FmTemperatureColours.class, "cold2hot", "Return a colour from cold (blue) to hot (red) based on the input number, which should be in the range 0 to 1.", "fraction");
// add distance functions
addStandardFunction(FmDrivingRouteGeomFromLine.class, "routegeom", "Given an input line geometry, calculate the road network route between the start and end." , "line_geometry" , "road_network_graph_directory");
String [] lat1lng1latlng2 = new String[]{"latitude1", "longitude1", "latitude2", "longitude2"};
for(String [] params : new String[][]{
new String[]{"geometry1", "geometry2"},
lat1lng1latlng2,
}){
addStandardFunction(FmDistance.Km.class, "distanceKm", "Calculate distance in kilometres between points.", params).setGroup("Distance");
addStandardFunction(FmDistance.Miles.class, "distanceMiles", "Calculate distance in miles between points.", params).setGroup("Distance");;
addStandardFunction(FmDistance.Metres.class, "distanceMetres", "Calculate distance in metres between points.", params).setGroup("Distance");;
String []extParams = Strings.addToArray(params, "road_network_graph_directory");
addStandardFunction(FmDrivingDistance.Km.class, "drivingDistanceKm", "Calculate driving distance in kilometres between points.", extParams).setGroup("DrivingDistance");
addStandardFunction(FmDrivingDistance.Miles.class, "drivingDistanceMiles", "Calculate driving distance in miles between points.", extParams).setGroup("DrivingDistance");
addStandardFunction(FmDrivingDistance.Metres.class, "drivingDistanceMetres", "Calculate driving distance in metres between points.", extParams).setGroup("DrivingDistance");
addStandardFunction(FmDrivingTime.class, "drivingTime", "Calculate driving time between points.", extParams);
addStandardFunction(FmDrivingTimeUncached.class, "drivingTimeUncached", "Calculate driving time between points without using caching.", extParams);
addStandardFunction(FmDrivingRouteGeom.class, "routegeom", "Calculate the road network route between points.", extParams);
addStandardFunction(FmDrivingRouteGeomUncached.class, "routegeomuncached", "Calculate the road network route between points without using caching", extParams);
}
// addStandardFunction(FmDrivingTime.class, "drivingTimeUncached", "Calculate driving time between points, do not cache results.", lat1lng1latlng2);_
// create time functions
addStandardFunction(FmTime.class, "time", "Create a time form the current system time.");
for(String [] params : new String[][]{
new String[]{"milliseconds"},
new String[]{"hours", "minutes"},
new String[]{"days", "hours", "minutes"},
new String[]{"days", "hours", "minutes", "seconds"},
new String[]{"days", "hours", "minutes", "seconds", "milliseconds"},
}){
addStandardFunction(FmTime.class, "time", "Create a time using the input components.",params).setGroup("Time");;
}
addStandardFunction(FmRound2Second.class, "round2second","Round the time to the nearest second.", "time").setGroup("Time");
// create geometry functions
for(final FmGeom.GeomType type:FmGeom.GeomType.values()){
FunctionDefinition dfn = new FunctionDefinition(FunctionType.FUNCTION, type.name().toLowerCase());
dfn.setDescription("Create a geometry object of type " + type.name().toLowerCase() + " from longitude and latitudes.");
if(type == GeomType.POINT){
dfn.addArg("Longitude");
dfn.addArg("Latitude");
}else{
dfn.addVarArgs("longAndLat", ArgumentType.GENERAL, "Pairs of longitude and latitude values.");
}
dfn.setFactory(new FunctionFactory() {
@Override
public Function createFunction(Function... children) {
return new FmGeom(type,children);
}
});
add(dfn);
}
addStandardFunction(FmLatitude.class, "latitude", "Return the latitude of a geometry. If the geometry is not a point this returns its centroid's latitude.","geometry");
addStandardFunction(FmLongitude.class, "longitude", "Return the longitude of a geometry. If the geometry is not a point this returns its centroid's longitude.","geometry");
addStandardFunction(FmCentroid.class, "centroid", "Return the centroid of the geometry, as a point geometry.","geometry");
addStandardFunction(FmShapefileLookup.class, "shapefilelookup", "Lookup a geometry in a shapefile on disk. For the input filename and type_name in the file, the search_value is searched for in the search_field and the geometry of the first matching record is returned.", "filename","search_value", "type_name", "search_field");
addStandardFunction(FmShapefileLookup.class, "shapefilelookup", "Lookup a geometry in a shapefile on disk. For the input filename and type_name in the file, the search_value is searched for in the search_field and the geometry of the first matching record is returned.", "filename","search_value", "search_field");
addStandardFunction(FmGeomBorder.class, "geomborder", "Return the borders of the geometry as lines.", "geometry", "include_holes");
addStandardFunction(FmGeomContains.class, "geomcontains", "Return whether the geometry contains the point, using the input EPSG grid projection.", "geometry", "latitude", "longitude", "EPSG");
addStandardFunction(FmGeomContains.class, "geomcontains", "Return whether the geometry contains the point, using a WGS84 latitude-longitude projection.", "geometry", "latitude", "longitude");
addStandardFunction(FmLineStringFraction.class, "linestringfraction", "Return a fraction (0 to 1) of the input linestring.", "linestring", "fraction");
addStandardFunction(FmLineStringEnd.class, "linestringend", "Return the end of a linestring (as another geometry).", "linestring");
addStandardFunction(FmTileFactory.class, "tileprovider", "Create a map tile provider", "String containing a comma-separated list of key-value pairs defining the background map - e.g. \"fade.r=255, type=MAPSFORGE\".");
addStandardFunction(FmDecimalHours.class, "decimalHours", "Return the number of decimal hours in a time.","time");
addStandardFunction(FmGeomArea.class, "geomarea", "Calculate the area of the geometry in the units of the input EPSG projection. The projection must be an equal area projection or the calculation will be wrong.", "geometry", "EPSGCodeEqualArea");
// uk postcodes
for(final UKPostcodeLevel level: UKPostcodeLevel.values()){
FunctionDefinition dfn = addStandardFunction(FmPostcodeUk.class, "postcodeuk" + level.name().toLowerCase(), "Find and return the first UK postcode " + level.name().toLowerCase() + " from the input string, or null if not found.", "input_string");
dfn.setFactory(new FunctionFactory() {
@Override
public Function createFunction(Function... children) {
return new FmPostcodeUk(level, children[0]);
}
});
dfn.setGroup("postcodeUK");
}
// min / max
class MinMaxHelper {
void build(final Class<? extends Function> cls, final String name, String description) {
FunctionDefinition dfn = new FunctionDefinition(name);
dfn.addVarArgs("values", ArgumentType.GENERAL, "");
dfn.setFactory(new FunctionFactory() {
@Override
public Function createFunction(Function... args) {
try {
Constructor<? extends Function> constructor = cls.getConstructor(Function[].class);
return constructor.newInstance((Object) args);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
});
dfn.setDescription(description);
add(dfn);
}
}
MinMaxHelper minMaxHelper = new MinMaxHelper();
minMaxHelper.build(FmMax.class, "max", "Return the maximum of the input arguments.");
minMaxHelper.build(FmMin.class, "min", "Return the minimum of the input arguments.");
// random data
class RandDataHelper{
void build( final String name, String description,final RandDataType type) {
FunctionDefinition dfn = new FunctionDefinition(name);
dfn.setFactory(new FunctionFactory() {
@Override
public Function createFunction(Function... args) {
try {
return new FmRandData(type);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
});
dfn.setDescription(description);
add(dfn);
}
}
RandDataHelper rDataHelper = new RandDataHelper();
rDataHelper.build("randPersonName", "Randomly generate a person's name", RandDataType.PERSON_NAME);
rDataHelper.build("randCompanyName", "Randomly generate a company's name", RandDataType.COMPANY_NAME);
rDataHelper.build("randStreetName", "Randomly generate a street name", RandDataType.STREET_NAME);
// first non null
FunctionDefinition firstNonNull = new FunctionDefinition("firstNonNull");
firstNonNull.addVarArgs("values", ArgumentType.GENERAL, "");
firstNonNull.setFactory(new FunctionFactory() {
@Override
public Function createFunction(Function... args) {
return new FmFirstNonNull(args);
}
});
firstNonNull.setDescription("Return the first non-null value");
add(firstNonNull);
// switch function
FunctionDefinition switchDfn = new FunctionDefinition("switch");
switchDfn.addVarArgs("expression, value1, result1, value2, result2, .... valueN, resultN, ... else", ArgumentType.GENERAL, "");
switchDfn.setDescription("Do a switch over the expression returning the result corresponding to the matching value or the else term at the end if provided.");
switchDfn.setFactory(new FunctionFactory() {
@Override
public Function createFunction(Function... children) {
if(children.length < 3){
throw new RuntimeException("Switch function must have at least 3 arguments.");
}
return new FmSwitch(children);
}
});
add(switchDfn);
// string functions
addStandardFunction(FmUpper.class, "upper", "Convert string to upper case.", "string_value");
addStandardFunction(FmLower.class, "lower", "Convert string to lower case.","string_value");
addStandardFunction(FmCreateUUID.class, "createUUID", "Create a type 3 (name based) UUID using the input string.","string_value");
addStandardFunction(FmLen.class, "len", "Get length of the string or 0 if string is null.","string_value");
addStandardFunction(FmLeft.class, "left","", "text", "number_of_chars");
addStandardFunction(FmContains.class, "contains","", "find_string", "find_within_string");
addStandardFunction(FmIndexOf.class, "indexof","", "find_string", "find_within_string");
addStandardFunction(FmReplace.class, "replace","", "find_within_string", "old_string", "new_string");
addStandardFunction(FmPostcodeUKFormatUnit.class, "postcodeukreformatunit", "Format a UK unit postcode, ensuring a single space between the two parts.", "raw_postcode").setGroup("postcodeUK");
addStandardFunction(FmRegExpMatches.class, "regexpmatch", "Return true if the string matched the regular expression.", "regular-expression" , "string");
// addStandardFunction(FmRegExpMatchedText.class, "regexpmatchedstring", "Return the string which matched the regular expression or null if no match.", "regular-expression" , "string");
addStandardFunction(FmRegExpMatchedGroup.class, "regexpmatchedgroup", "Return the ith group in the regular expression, assuming it matched, or null if no match. This uses a zero-based index.", "regular-expression" , "string", "group_index");
FunctionDefinition formatDfn = new FunctionDefinition(FunctionType.FUNCTION, "stringformat");
formatDfn.addArg("FormatString");
formatDfn.addVarArgs("Args", ArgumentType.GENERAL, "Format arguments");
formatDfn.setDescription("String formatter equivalent to Java's String.Format.");
formatDfn.setFactory(new FunctionFactory() {
@Override
public Function createFunction(Function... children) {
return new FmStringFormat(children[0], Arrays.copyOfRange(children, 1, children.length));
}
});
add(formatDfn);
addStandardFunction(FmStringDateTimeStamp.class, "Timestamp", "Creates a string timestamp suitable for use in filenames.");
// decimal format
FunctionDefinition dfn = new FunctionDefinition(FunctionType.FUNCTION, "decimalformat");
dfn.setDescription("Format a double number using the input formatting pattern, which is the same as Java's DecimalFormat class");
dfn.addArg("format_string", ArgumentType.STRING_CONSTANT);
dfn.addArg("number");
dfn.setFactory(createReflectionFactory(FmDecimalFormat.class, "decimalformat"));
add(dfn);
// constants
addConstant("true", new FmConst(1L), null);
addConstant("false", new FmConst(0L), null);
addConstant("pi", new FmConst(Math.PI), null);
addConstant("e", new FmConst(Math.E), null);
addConstant("null", new FmConst((Object)null), null);
// map flag labels
addConstant("mfAlwaysShowLabel", new FmConst(UserRenderFlags.ALWAYS_SHOW_LABEL), "Flag which forces the map to always show a label even if it overlaps others.");
addConstant("mfDotDashLine", new FmConst(UserRenderFlags.DOT_DASH_LINE), "Flag which forces lines to be drawn with dots and dashes _ . _ . _ .");
addConstant("mfDottedLine", new FmConst(UserRenderFlags.DOTTED_LINE), "Flag which forces lines to be drawn with dots. . . . . .");
for(Map.Entry<String, Color> entry : Colours.getStandardColoursMap().entrySet()){
addConstant(entry.getKey(),new FmConst( entry.getValue()), "Standard colour").setGroup("colours");
}
// addTagConstants();
return this;
}
// private void addTagConstants(){
// for(Field field : PredefinedTags.class.getDeclaredFields()){
// if(field.getAnnotation(PredefinedTags.ODLConstFunction.class)!=null){
// String name = field.getName().toLowerCase();
// try {
// String value = (String)field.get(null);
// addConstant(name, new FmConst(value), "Predefined tag, equal to the string \"" + value + "\".").setGroup("predefined tags");
// } catch (Exception e) {
//
// }
// }
// }
// }
private void addStandardOperator(Class<? extends Function> cls, String symbol, String description) {
addOperator(createReflectionFactory(cls, symbol), symbol,description);
}
private void addOperator(FunctionFactory factory, String symbol, String description) {
FunctionDefinition dfn = new FunctionDefinition(FunctionType.OPERATOR, symbol);
dfn.setOperatorPrecendence(nextOperatorPrecedence++);
dfn.setFactory(factory);
dfn.setDescription(description);
// operators always have 2 arguments...
for(int i = 1 ; i<=2 ; i++){
dfn.addArg("value" + Integer.toString(i));
}
add(dfn);
}
public FunctionDefinition identifyOperator(final String name) {
final List<FunctionDefinition> list = map.get(name);
if (list == null) {
return null;
}
for(FunctionDefinition dfn:list){
if(dfn.getType() == FunctionType.OPERATOR){
return dfn;
}
}
return null;
}
public FunctionFactory identify(final String name, final FunctionType type) {
// Get the list of function definitions with this name
final List<FunctionDefinition> list = map.get(name);
if (list == null) {
return null;
}
// If we have one or more functions then return a functionfactory which chooses the one with the correct number of parameters
return new FunctionFactory() {
@Override
public Function createFunction(Function... children) {
// find function with correct number of arguments
int nbCorrectType = 0;
for (FunctionDefinition dfn : list) {
if (dfn.getType() == type) {
nbCorrectType++;
if (children.length == dfn.nbArgs()) {
// check each argument
for(int i =0 ; i<children.length ; i++){
FunctionArgument arg = dfn.getArg(i);
// check constant strings are correct
if(arg.isConstantString() && FunctionUtils.getConstantString(children[i])==null){
throw new RuntimeException("Argument " + (i+1) + " passed into function " + name + " is not a constant string.");
}
// check table references are formatted correctly (a table ref is a type of constant string)
if(arg.getArgumentType() == ArgumentType.TABLE_REFERENCE_CONSTANT){
ExecutionReportImpl res = new ExecutionReportImpl();
String s = FunctionUtils.getConstantString(children[i]);
if(TableReference.create(s, res)==null){
throw new RuntimeException(res.getReportString(false,true));
}
}
}
// every is OK
return dfn.getFactory().createFunction(children);
} else if (dfn.hasVarArgs()) {
// we assume a function with variable arguments is *entirely* variable arguments..
return dfn.getFactory().createFunction(children);
}
}
}
if (nbCorrectType > 0) {
throw new RuntimeException("Incorrect number of arguments passed into function: " + Strings.std(name));
}
return null;
}
};
}
/**
* Add a standard function. A standard function can be instantiated using reflection
* with a constructor taking a fixed number of Function objects.
* @param cls
* @param name
* @param argNames
*/
public FunctionDefinition addStandardFunction(Class<? extends Function> cls, String name,String description, String... argNames) {
FunctionDefinition dfn = new FunctionDefinition(FunctionType.FUNCTION, name);
dfn.setDescription(description);
for (String argName : argNames) {
dfn.addArg(argName);
}
dfn.setFactory(createReflectionFactory(cls, name));
add(dfn);
return dfn;
}
private FunctionDefinition addConstant(final String name, final FmConst val, final String description) {
FunctionDefinition dfn = new FunctionDefinition(FunctionType.CONSTANT, name);
if(description!=null){
dfn.setDescription(description);
}else{
dfn.setDescription("Constant value equal to " + val.toString());
}
dfn.setFactory(new FunctionFactory() {
@Override
public Function createFunction(Function... children) {
return val.deepCopy();
}
});
add(dfn);
return dfn;
}
private FunctionFactory createReflectionFactory(final Class<? extends Function> cls, final String name) {
return new FunctionFactory() {
@Override
public Function createFunction(Function... args) {
try {
// create arrays holding arg classes and args cast to objects
Class<?>[] constructorArgsCls = new Class<?>[args.length];
Object[] oargs = new Object[args.length];
for (int i = 0; i < args.length; i++) {
constructorArgsCls[i] = Function.class;
oargs[i] = args[i];
}
// get constructor
Constructor<?> constructor = cls.getConstructor(constructorArgsCls);
Function formula = (Function) constructor.newInstance(oargs);
return formula;
} catch (Throwable e) {
throwIncorrectNbArgs(name);
return null;
}
}
};
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (FunctionType type : FunctionType.values()) {
for (List<FunctionDefinition> list : map.values()) {
for (FunctionDefinition dfn : list) {
if (dfn.getType() == type) {
builder.append(dfn.toString() + System.lineSeparator());
}
}
}
}
return builder.toString();
}
/**
* Some of the operator formula classes can take variable arguments BUT we currently only allow the user formula to pass 2 parameters. This method
* deals with this case.
*
* @param cls
* @return
*/
private void addVargsOperator(final Class<? extends Function> cls, final String symbol, String description) {
addOperator(new FunctionFactory() {
@Override
public Function createFunction(Function... args) {
if (args.length != 2) {
throw new RuntimeException("Operator requires 2 arguments: " + symbol);
}
try {
Constructor<? extends Function> constructor = cls.getConstructor(Function[].class);
return constructor.newInstance((Object) args);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}, symbol,description);
}
private void throwIncorrectNbArgs(final String name) {
throw new RuntimeException("Could not instantiate function " + name + ". Is the number of arguments correct?");
}
/**
* Get all definitions in a list, ordered by type first
* @return
*/
public List<FunctionDefinition> toList(){
ArrayList<FunctionDefinition> ret= new ArrayList<>();
for (FunctionType type : FunctionType.values()) {
for (List<FunctionDefinition> list : map.values()) {
for (FunctionDefinition dfn : list) {
if (dfn.getType() == type) {
ret.add(dfn);
}
}
}
}
return ret;
}
}