From 562badc796cb5c6f95d811e7ac26626748800761 Mon Sep 17 00:00:00 2001 From: Francis Dong Date: Fri, 20 Aug 2021 17:06:22 +0800 Subject: [PATCH] Support function in aggregation #281 --- fizz-core/pom.xml | 5 + .../main/java/we/fizz/function/CodecFunc.java | 184 +++++++ .../main/java/we/fizz/function/DateFunc.java | 246 +++++++++ .../java/we/fizz/function/FuncExecutor.java | 474 ++++++++++++++++++ .../src/main/java/we/fizz/function/IFunc.java | 39 ++ .../main/java/we/fizz/function/ListFunc.java | 124 +++++ .../main/java/we/fizz/function/MathFunc.java | 162 ++++++ .../java/we/fizz/function/StringFunc.java | 148 ++++++ .../main/java/we/fizz/input/PathMapping.java | 44 +- .../java/we/fizz/function/CodecFuncTests.java | 125 +++++ .../java/we/fizz/function/DateFuncTests.java | 69 +++ .../java/we/fizz/function/ListFuncTests.java | 133 +++++ .../java/we/fizz/function/MathFuncTests.java | 179 +++++++ .../we/fizz/function/StringFuncTests.java | 110 ++++ pom.xml | 6 + 15 files changed, 2032 insertions(+), 16 deletions(-) create mode 100644 fizz-core/src/main/java/we/fizz/function/CodecFunc.java create mode 100644 fizz-core/src/main/java/we/fizz/function/DateFunc.java create mode 100644 fizz-core/src/main/java/we/fizz/function/FuncExecutor.java create mode 100644 fizz-core/src/main/java/we/fizz/function/IFunc.java create mode 100644 fizz-core/src/main/java/we/fizz/function/ListFunc.java create mode 100644 fizz-core/src/main/java/we/fizz/function/MathFunc.java create mode 100644 fizz-core/src/main/java/we/fizz/function/StringFunc.java create mode 100644 fizz-core/src/test/java/we/fizz/function/CodecFuncTests.java create mode 100644 fizz-core/src/test/java/we/fizz/function/DateFuncTests.java create mode 100644 fizz-core/src/test/java/we/fizz/function/ListFuncTests.java create mode 100644 fizz-core/src/test/java/we/fizz/function/MathFuncTests.java create mode 100644 fizz-core/src/test/java/we/fizz/function/StringFuncTests.java diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml index 3691a2c..ba1f87f 100644 --- a/fizz-core/pom.xml +++ b/fizz-core/pom.xml @@ -278,6 +278,11 @@ commons-codec commons-codec + + + commons-beanutils + commons-beanutils + diff --git a/fizz-core/src/main/java/we/fizz/function/CodecFunc.java b/fizz-core/src/main/java/we/fizz/function/CodecFunc.java new file mode 100644 index 0000000..ca57020 --- /dev/null +++ b/fizz-core/src/main/java/we/fizz/function/CodecFunc.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package we.fizz.function; + +import java.io.UnsupportedEncodingException; +import java.security.Key; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import we.util.DigestUtils; + +/** + * Codec Functions + * + * @author Francis Dong + * + */ +public class CodecFunc implements IFunc { + + private static final Logger LOGGER = LoggerFactory.getLogger(CodecFunc.class); + + private static final String CHARSET_UTF8 = "UTF-8"; + + private static final String IV = "12345678"; + + private static CodecFunc singleton; + + public static CodecFunc getInstance() { + if (singleton == null) { + synchronized (CodecFunc.class) { + if (singleton == null) { + CodecFunc instance = new CodecFunc(); + instance.init(); + singleton = instance; + } + } + } + return singleton; + } + + private CodecFunc() { + } + + public void init() { + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.md5", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha1", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha256", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha384", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha512", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.base64Encode", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.base64Decode", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.aesEncrypt", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.aesDecrypt", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.desEncrypt", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "codec.desDecrypt", this); + } + + public String md5(String data) { + return DigestUtils.md5Hex(data); + } + + public String sha1(String data) { + return DigestUtils.sha1Hex(data); + } + + public String sha256(String data) { + return DigestUtils.sha256Hex(data); + } + + public String sha384(String data) { + return DigestUtils.sha384Hex(data); + } + + public String sha512(String data) { + return DigestUtils.sha512Hex(data); + } + + public String base64Encode(String data) throws Exception { + try { + return Base64.getEncoder().encodeToString(data.getBytes(CHARSET_UTF8)); + } catch (UnsupportedEncodingException e) { + LOGGER.error("Base64 encode error, data={}", data, e); + throw e; + } + } + + public String base64Decode(String data) throws Exception { + return new String(Base64.getDecoder().decode(data)); + } + + public String aesEncrypt(String data, String key) throws Exception { + if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) { + return null; + } + try { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(CHARSET_UTF8), "AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + byte[] result = cipher.doFinal(data.getBytes(CHARSET_UTF8)); + return Base64.getEncoder().encodeToString(result); + } catch (Exception e) { + LOGGER.error("AES encrypt error, data={}", data, e); + throw e; + } + } + + public String aesDecrypt(String data, String key) throws Exception { + if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) { + return null; + } + try { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(CHARSET_UTF8), "AES"); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + byte[] result = Base64.getDecoder().decode(data); + return new String(cipher.doFinal(result)); + } catch (Exception e) { + LOGGER.error("AES decrypt error, data={}", data, e); + throw e; + } + } + + public String desEncrypt(String data, String key) throws Exception { + if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) { + return null; + } + try { + DESKeySpec dks = new DESKeySpec(key.getBytes(CHARSET_UTF8)); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + Key secretKey = keyFactory.generateSecret(dks); + Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); + IvParameterSpec iv = new IvParameterSpec(IV.getBytes(CHARSET_UTF8)); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); + byte[] bytes = cipher.doFinal(data.getBytes(CHARSET_UTF8)); + return new String(Base64.getEncoder().encode(bytes)); + } catch (Exception e) { + LOGGER.error("DES eecrypt error, data={}", data, e); + throw e; + } + } + + public String desDecrypt(String data, String key) throws Exception { + if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) { + return null; + } + try { + DESKeySpec dks = new DESKeySpec(key.getBytes(CHARSET_UTF8)); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + Key secretKey = keyFactory.generateSecret(dks); + Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); + IvParameterSpec iv = new IvParameterSpec(IV.getBytes(CHARSET_UTF8)); + cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); + return new String(cipher.doFinal(Base64.getDecoder().decode(data.getBytes(CHARSET_UTF8))), CHARSET_UTF8); + } catch (Exception e) { + LOGGER.error("DES decrypt error, data={}", data, e); + throw e; + } + } + +} diff --git a/fizz-core/src/main/java/we/fizz/function/DateFunc.java b/fizz-core/src/main/java/we/fizz/function/DateFunc.java new file mode 100644 index 0000000..2514166 --- /dev/null +++ b/fizz-core/src/main/java/we/fizz/function/DateFunc.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package we.fizz.function; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import we.fizz.exception.FizzRuntimeException; + +/** + * Date Functions + * + * @author Francis Dong + * + */ +public class DateFunc implements IFunc { + + private static final Logger LOGGER = LoggerFactory.getLogger(DateFunc.class); + + private static DateFunc singleton; + + public static DateFunc getInstance() { + if (singleton == null) { + synchronized (DateFunc.class) { + if (singleton == null) { + DateFunc instance = new DateFunc(); + instance.init(); + singleton = instance; + } + } + } + return singleton; + } + + private DateFunc() { + } + + public void init() { + FuncExecutor.register(NAME_SPACE_PREFIX + "date.timestamp", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "date.now", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "date.add", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "date.formatTs", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "date.changePattern", this); + } + + /** + * Date pattern
+ * yyyy-MM-dd + */ + public final static String DATE_FORMAT = "yyyy-MM-dd"; + + /** + * Time pattren
+ * HH:mm:ss + */ + public final static String TIME_FORMAT = "HH:mm:ss"; + + /** + * Short time pattren
+ * HH:mm + */ + public final static String SHORT_TIME_FORMAT = "HH:mm"; + + /** + * Date time pattern
+ * yyyy-MM-dd HH:mm:ss + */ + public final static String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + /** + * Returns current timestamp (Milliseconds) + * + * @return + */ + public long timestamp() { + return System.currentTimeMillis(); + } + + /** + * Returns current time with the given pattern
+ * Frequently-used pattern:
+ * yyyy-MM-dd HH:mm:ss
+ * yyyy-MM-dd
+ * HH:mm:ss
+ * HH:mm
+ * yyyy-MM-dd HH:mm:ss Z
+ * + * @param pattern [optional] the pattern describing the date and time format, + * dafault yyyy-MM-dd HH:mm:ss + * @return + */ + public String now(String pattern) { + return formatDate(new Date(), pattern); + } + + /** + * Adds or subtracts the specified amount of time to the given calendar field, + * based on the calendar's rules. For example, to subtract 5 hours from the + * current time of the calendar, you can achieve it by calling: + *

+ * add("2021-08-04 14:23:12", "yyyy-MM-dd HH:mm:ss", 4, -5). + * + * @param date date string + * @param pattern date pattern of the given date string + * @param field the calendar field,
+ * 1 for millisecond
+ * 2 for second
+ * 3 for minute
+ * 4 for hour
+ * 5 for date
+ * 6 for month
+ * 7 for year
+ * @param amount the amount of date or time to be added to the field + * @return + */ + public String add(String date, String pattern, int field, int amount) { + Date d = parse(date, pattern); + if (d != null) { + // convert to calendar field + int calField = 0; + switch (field) { + case 1: + calField = Calendar.MILLISECOND; + break; + case 2: + calField = Calendar.SECOND; + break; + case 3: + calField = Calendar.MINUTE; + break; + case 4: + calField = Calendar.HOUR; + break; + case 5: + calField = Calendar.DATE; + break; + case 6: + calField = Calendar.MONTH; + break; + case 7: + calField = Calendar.YEAR; + break; + default: + LOGGER.error("invalid field, date={} pattern={} filed={}", date, pattern, field); + throw new FizzRuntimeException( + "invalid field, date=" + date + "pattern=" + pattern + " filed=" + field); + } + return formatDate(addToFiled(d, calField, amount), pattern); + } + return null; + } + + /** + * Format the a timestamp to the given pattern + * + * @param timestamp + * @param pattern + * @return + */ + public String formatTs(long timestamp, String pattern) { + return formatDate(new Date(timestamp), pattern); + } + + /** + * Format the a time with source pattern to the target pattern + * + * @param dateStr date + * @param sourcePattern source pattern + * @param targetPattern target pattern + * @return + */ + public String changePattern(String dateStr, String sourcePattern, String targetPattern) { + return formatDate(parse(dateStr, sourcePattern), targetPattern); + } + + /** + * Adds or subtracts the specified amount of time to the given calendar field + * + * @param date a Date + * @param field field that the times to be add to, such as: Calendar.SECOND, + * Calendar.YEAR + * @param amount the amount of date or time to be added to the field + * @return + */ + private Date addToFiled(Date date, int field, int amount) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(field, amount); + return cal.getTime(); + } + + /** + * Parse string to Date + * + * @param dateStr String to be parsed + * @param pattern pattern of dateStr + * @return + */ + private Date parse(String dateStr, String pattern) { + SimpleDateFormat df = new SimpleDateFormat(pattern == null ? DATE_TIME_FORMAT : pattern); + try { + return df.parse(dateStr); + } catch (ParseException e) { + LOGGER.error("Parse date error, dateStr={} pattern={}", dateStr, pattern, e); + throw new FizzRuntimeException("Parse date error, dateStr=" + dateStr + " pattern=" + pattern, e); + } + } + + /** + * Format date with the given pattern
+ * Frequently-used pattern:
+ * yyyy-MM-dd HH:mm:ss
+ * yyyy-MM-dd
+ * HH:mm:ss
+ * HH:mm
+ * yyyy-MM-dd HH:mm:ss Z
+ * + * @param pattern [optional] the pattern describing the date and time format, + * dafault yyyy-MM-dd HH:mm:ss + * @return + */ + private String formatDate(Date date, String pattern) { + SimpleDateFormat sdf = new SimpleDateFormat(pattern == null ? DATE_TIME_FORMAT : pattern); + return sdf.format(date); + } + +} diff --git a/fizz-core/src/main/java/we/fizz/function/FuncExecutor.java b/fizz-core/src/main/java/we/fizz/function/FuncExecutor.java new file mode 100644 index 0000000..e81709a --- /dev/null +++ b/fizz-core/src/main/java/we/fizz/function/FuncExecutor.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.fizz.function; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.commons.lang3.StringUtils; +import org.noear.snack.ONode; +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import we.fizz.exception.FizzRuntimeException; +import we.fizz.input.Input; +import we.fizz.input.PathMapping; + +/** + * Function Register + * + * @author Francis Dong + * + */ +public class FuncExecutor { + + private static final Logger LOGGER = LoggerFactory.getLogger(FuncExecutor.class); + + private static final Map funcMap = new HashMap<>(); + + private static Pattern NUMBER_PATTERN = Pattern + .compile("^[-\\+]?[\\d]+\\s*[,\\)]{1}|^[-\\+]?[\\d]+\\.[\\d]+\\s*[,\\)]{1}"); + + private static FuncExecutor singleton; + + public static FuncExecutor getInstance() { + if (singleton == null) { + synchronized (FuncExecutor.class) { + if (singleton == null) { + singleton = new FuncExecutor(); + init(); + } + } + } + return singleton; + } + + private FuncExecutor() { + } + + public static void init() { + try { + Reflections reflections = new Reflections("we.fizz.function"); + Set> types = reflections.getSubTypesOf(IFunc.class); + for (Class fnType : types) { + Method method = fnType.getMethod("getInstance"); + method.invoke(fnType); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Register a function instance + * + * @param namespace a name to identify the given function instance + * @param func + */ + public static void register(String namespace, IFunc funcInstance) { + if (StringUtils.isBlank(namespace)) { + LOGGER.warn("namespace is required"); + return; + } + if (!namespace.startsWith(IFunc.NAME_SPACE_PREFIX)) { + LOGGER.warn("namespace must start with fn."); + return; + } + if (funcInstance == null) { + LOGGER.warn("function instance is required"); + return; + } + funcMap.put(namespace, funcInstance); + } + + /** + * Execute function + * + * @param funcExpression + * @return + */ + public Object exec(ONode ctxNode, String funcExpression) { + RecursionContext ctx = new RecursionContext(); + ctx.setFuncExpression(funcExpression); + return doExec(ctxNode, ctx); + } + + private Object doExec(ONode ctxNode, RecursionContext ctx) { + String funcExpression = ctx.funcExpression; + if (StringUtils.isBlank(funcExpression)) { + return null; + } + funcExpression = StringUtils.trim(funcExpression); + int pos1 = funcExpression.indexOf("("); + if (pos1 == -1) { + LOGGER.warn("func expression is invalid, expression: {}", funcExpression); + return null; + } + if (!funcExpression.endsWith(")")) { + LOGGER.warn("func expression is invalid, expression: {}", funcExpression); + return null; + } + + String path = funcExpression.substring(0, pos1); + int lastDotPos = path.lastIndexOf("."); + if (pos1 == -1) { + LOGGER.warn("func expression is invalid, expression: {}", funcExpression); + return null; + } + String namespace = path.substring(0, lastDotPos); + String methodName = path.substring(lastDotPos + 1); + + Object funcInstance = funcMap.get(path); + if (funcInstance == null) { + String msg = String.format("function not found: %s, expression: %s", path, funcExpression); + LOGGER.warn(msg); + throw new FizzRuntimeException(msg); + } + + try { + Method method = findMethod(funcInstance.getClass(), methodName); + Class[] paramTypes = method.getParameterTypes(); + ctx.funcExpression = funcExpression; + Object[] args = parseArgs(ctxNode, ctx, funcExpression, paramTypes, method.isVarArgs()); + if (args == null) { + return method.invoke(funcInstance); + } + return method.invoke(funcInstance, args); + } catch (FizzRuntimeException e) { + throw e; + } catch (InvocationTargetException e) { + Throwable targetEx = e.getTargetException(); + if (targetEx instanceof FizzRuntimeException) { + throw (FizzRuntimeException) targetEx; + } + String msg = targetEx.getMessage(); + if (msg == null) { + msg = String.format("execute function error: %s", funcExpression); + } + LOGGER.error(msg, targetEx); + throw new FizzRuntimeException(msg, targetEx); + } catch (Exception e) { + String msg = String.format("execute function error: %s", funcExpression); + LOGGER.error(msg, e); + throw new FizzRuntimeException(msg, e); + } + } + + private Method findMethod(Class funcClass, String methodName) { + Method[] methods = funcClass.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equals(methodName)) { + return method; + } + } + String msg = String.format("method not found: %s, class: %s", methodName, funcClass); + LOGGER.warn(msg); + throw new FizzRuntimeException(msg); + } + + /** + * funcExpression sample:
+ * fn.date.add({step1.request1.response.body.date}, "yyyy-MM-dd HH:mm:ss", 1, + * 1000)
+ * fn.date.add(true, fn.date.add({step1.request1.response.body.date}, + * "yyyy-MM-dd HH:mm:ss", 1, 1000), "yyyy-MM-dd HH:mm:ss\"))}}", 1, 1000)
+ * + * @param funcExpression + * @param paramTypes + * @return + */ + private Object[] parseArgs(ONode ctxNode, RecursionContext ctx, String funcExpression, Class[] paramTypes, + boolean isVarArgs) { + int pos1 = funcExpression.indexOf("("); + // int pos2 = funcExpression.lastIndexOf(")"); + String argsStr = funcExpression.substring(pos1 + 1); + argsStr = StringUtils.trim(argsStr); + // check if there is any argument + if (StringUtils.isBlank(argsStr)) { + if (paramTypes == null || paramTypes.length == 0) { + return null; + } else if (paramTypes.length == 1 && isVarArgs) { + // check if variable arguments + return null; + } else { + throw new FizzRuntimeException( + String.format("missing argument, Function Expression: %s", funcExpression)); + } + } + Object[] args = new Object[paramTypes.length]; + List varArgs = new ArrayList<>(); + for (int i = 0; i < paramTypes.length; i++) { + Class clazz = paramTypes[i]; + if (StringUtils.isBlank(argsStr)) { + if (isVarArgs && i == paramTypes.length - 1 && args[i] == null) { + args[i] = Array.newInstance(clazz.getComponentType(), 0); + } + break; + } + ArgsStrContainer argsStrContainer = new ArgsStrContainer(argsStr, i); + if (argsStr.startsWith("\"")) { // string + int pos = findStringEngPos(argsStr); + if (pos != -1) { + String arg = argsStr.substring(1, pos); + if (isVarArgs && i == paramTypes.length - 1) { + varArgs.add(arg); + args[i] = varArgs.toArray(new String[varArgs.size()]); + } else { + args[i] = arg; + } + argsStrContainer = this.trimArgStr(argsStrContainer, pos + 1, isVarArgs, paramTypes.length, + funcExpression); + argsStr = argsStrContainer.getArgsStr(); + i = argsStrContainer.getIndex(); + } else { + throw new FizzRuntimeException( + String.format("invalid argument: %s, Function Expression: %s", argsStr, funcExpression)); + } + } else if (argsStr.matches("^true\\s*,")) { // boolean + if (isVarArgs && i == paramTypes.length - 1) { + varArgs.add(true); + args[i] = varArgs.toArray(new Boolean[varArgs.size()]); + } else { + args[i] = true; + } + argsStrContainer = this.trimArgStr(argsStrContainer, 4, isVarArgs, paramTypes.length, funcExpression); + argsStr = argsStrContainer.getArgsStr(); + i = argsStrContainer.getIndex(); + } else if (argsStr.matches("^false\\s*,")) { // boolean + if (isVarArgs && i == paramTypes.length - 1) { + varArgs.add(false); + args[i] = varArgs.toArray(new Boolean[varArgs.size()]); + } else { + args[i] = false; + } + argsStrContainer = this.trimArgStr(argsStrContainer, 5, isVarArgs, paramTypes.length, funcExpression); + argsStr = argsStrContainer.getArgsStr(); + i = argsStrContainer.getIndex(); + } else if (argsStr.startsWith("{")) { // reference value + int pos = argsStr.indexOf("}", 1); + if (pos != -1) { + String refKey = argsStr.substring(1, pos); + Object arg = PathMapping.getValueByPath(ctxNode, refKey); + arg = ConvertUtils.convert(arg, clazz); + if (isVarArgs && i == paramTypes.length - 1) { + varArgs.add(arg); + Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size()); + for (int j = 0; j < varArgs.size(); j++) { + Array.set(arr, j, varArgs.get(j)); + } + args[i] = arr; + + } else { + args[i] = arg; + } + + argsStrContainer = this.trimArgStr(argsStrContainer, pos + 1, isVarArgs, paramTypes.length, + funcExpression); + argsStr = argsStrContainer.getArgsStr(); + i = argsStrContainer.getIndex(); + } else { + throw new FizzRuntimeException( + String.format("invalid argument: %s, Function Expression: %s", argsStr, funcExpression)); + } + } else { + Matcher m = NUMBER_PATTERN.matcher(argsStr); + boolean isNumber = m.find(); + if (isNumber) { + int pos = m.end(); + String matchedStr = m.group(); + // Number + String strNum = StringUtils.trim(matchedStr.substring(0, pos - 1)); + if (isVarArgs && i == paramTypes.length - 1) { + Object arg = ConvertUtils.convert(strNum, clazz.getComponentType()); + varArgs.add(arg); + Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size()); + for (int j = 0; j < varArgs.size(); j++) { + Array.set(arr, j, varArgs.get(j)); + } + args[i] = arr; + } else { + Object arg = ConvertUtils.convert(strNum, clazz); + args[i] = arg; + } + argsStrContainer = this.trimArgStr(argsStrContainer, pos - 1, isVarArgs, paramTypes.length, + funcExpression); + argsStr = argsStrContainer.getArgsStr(); + i = argsStrContainer.getIndex(); + } else { + // function + ctx.funcExpression = argsStr; + Object rs = doExec(ctxNode, ctx); + if (isVarArgs && i == paramTypes.length - 1) { + Object arg = ConvertUtils.convert(rs, clazz.getComponentType()); + varArgs.add(arg); + Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size()); + for (int j = 0; j < varArgs.size(); j++) { + Array.set(arr, j, varArgs.get(j)); + } + args[i] = arr; + } else { + Object arg = ConvertUtils.convert(rs, clazz); + args[i] = arg; + } + argsStr = ctx.funcExpression; + argsStrContainer.setArgsStr(argsStr); + argsStrContainer = this.trimArgStr(argsStrContainer, 0, isVarArgs, paramTypes.length, + funcExpression); + argsStr = argsStrContainer.getArgsStr(); + i = argsStrContainer.getIndex(); + } + } + ctx.funcExpression = argsStr; + } + return args; + } + + private ArgsStrContainer trimArgStr(ArgsStrContainer argsStrContainer, int fromIndex, boolean isVarArgs, + int paramTypesLen, String funcExpression) { + int i = argsStrContainer.getIndex(); + String argsStr = argsStrContainer.getArgsStr(); + if (i == paramTypesLen - 1 || (isVarArgs && i == paramTypesLen - 2)) { + boolean hasMore = hasMoreArg(argsStr, fromIndex); + if (isVarArgs && hasMore) { + argsStr = removeComma(argsStr, fromIndex, funcExpression); + if (i == paramTypesLen - 1) { + i--; + } + } else { + if (hasCloseParenthesis(argsStr, fromIndex)) { + argsStr = removeCloseParenthesis(argsStr, fromIndex, funcExpression); + } else { + throw new FizzRuntimeException(String.format("invalid argument: %s, Function Expression: %s", + argsStr.substring(fromIndex), funcExpression)); + } + } + } else { + argsStr = removeComma(argsStr, fromIndex, funcExpression); + } + argsStrContainer.setArgsStr(argsStr); + argsStrContainer.setIndex(i); + return argsStrContainer; + } + + private boolean hasMoreArg(String argsStr, int fromIndex) { + final int strLen = argsStr.length(); + if (strLen == 0) { + return false; + } + for (int i = fromIndex; i < strLen; i++) { + if (!Character.isWhitespace(argsStr.charAt(i))) { + if (",".equals(String.valueOf(argsStr.charAt(i)))) { + return true; + } else { + return false; + } + } + } + return false; + } + + private boolean hasCloseParenthesis(String argsStr, int fromIndex) { + final int strLen = argsStr.length(); + if (strLen == 0) { + return false; + } + for (int i = fromIndex; i < strLen; i++) { + if (!Character.isWhitespace(argsStr.charAt(i))) { + if (")".equals(String.valueOf(argsStr.charAt(i)))) { + return true; + } else { + return false; + } + } + } + return false; + } + + private String removeComma(String argsStr, int fromIndex, String funcExpression) { + final int strLen = argsStr.length(); + if (strLen == 0) { + return argsStr; + } + for (int i = fromIndex; i < strLen; i++) { + if (!Character.isWhitespace(argsStr.charAt(i))) { + if (",".equals(String.valueOf(argsStr.charAt(i)))) { + return StringUtils.trim(argsStr.substring(i + 1)); + } + } + } + throw new FizzRuntimeException(String.format("missing comma after argument: %s, Function Expression: %s", + argsStr.substring(fromIndex), funcExpression)); + } + + private String removeCloseParenthesis(String argsStr, int fromIndex, String funcExpression) { + final int strLen = argsStr.length(); + if (strLen == 0 || strLen < fromIndex) { + return argsStr; + } + for (int i = fromIndex; i < strLen; i++) { + if (!Character.isWhitespace(argsStr.charAt(i))) { + if (")".equals(String.valueOf(argsStr.charAt(i)))) { + return StringUtils.trim(argsStr.substring(i + 1)); + } + } + } + throw new FizzRuntimeException( + String.format("missing close parenthesis after argument: %s, Function Expression: %s", + argsStr.substring(fromIndex), funcExpression)); + } + + private int findStringEngPos(String ep) { + int pos = ep.indexOf("\"", 1); + while (pos != -1) { + String prevChar = ep.substring(pos - 1, pos); + if (!"\\".equals(prevChar)) { + return pos; + } + pos = ep.indexOf("\"", pos); + } + return -1; + } + +} + +@Data +@AllArgsConstructor +class ArgsStrContainer { + private String argsStr; + private int index; +} + +@Data +@AllArgsConstructor +@NoArgsConstructor +class RecursionContext { + public String funcExpression; + public Object result; +} diff --git a/fizz-core/src/main/java/we/fizz/function/IFunc.java b/fizz-core/src/main/java/we/fizz/function/IFunc.java new file mode 100644 index 0000000..2b7882e --- /dev/null +++ b/fizz-core/src/main/java/we/fizz/function/IFunc.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.fizz.function; + +/** + * Function interface + * + * @author Francis Dong + * + */ +public interface IFunc { + + public final static String NAME_SPACE_PREFIX = "fn."; + + /** + * Init: Register functions to FuncExecutor in the initial stage
+ *
+ * Example:
+ * FuncExecutor.register(NAME_SPACE_PREFIX + "date.timestamp", getInstance());
+ * FuncExecutor.register(NAME_SPACE_PREFIX + "date.now", getInstance());
+ * + */ + void init(); + +} diff --git a/fizz-core/src/main/java/we/fizz/function/ListFunc.java b/fizz-core/src/main/java/we/fizz/function/ListFunc.java new file mode 100644 index 0000000..13d2163 --- /dev/null +++ b/fizz-core/src/main/java/we/fizz/function/ListFunc.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package we.fizz.function; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * List Functions + * + * @author Francis Dong + * + */ +public class ListFunc implements IFunc { + + private static final Logger LOGGER = LoggerFactory.getLogger(ListFunc.class); + + private static ListFunc singleton; + + public static ListFunc getInstance() { + if (singleton == null) { + synchronized (ListFunc.class) { + if (singleton == null) { + ListFunc instance = new ListFunc(); + instance.init(); + singleton = instance; + } + } + } + return singleton; + } + + private ListFunc() { + } + + public void init() { + FuncExecutor.register(NAME_SPACE_PREFIX + "list.expand", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "list.merge", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "list.extract", this); + } + + /** + * Expand sublist item to the first level + * + * @param data + * @return + */ + public List expand(List> data) { + List result = new ArrayList<>(); + if (data == null || data.size() == 0) { + return result; + } + for (List list : data) { + result.addAll(list); + } + return result; + } + + /** + * Merge multiple list into one list + * + * @param data + * @return + */ + public List merge(List... data) { + List result = new ArrayList<>(); + if (data == null || data.length == 0) { + return result; + } + for (List list : data) { + if (list == null || list.size() == 0) { + continue; + } + result.addAll(list); + } + return result; + } + + /** + * Extract fields from list + * + * @param data + * @param fields + * @return + */ + public List> extract(List> data, String... fields) { + List> result = new ArrayList<>(); + if (data == null || data.size() == 0) { + return result; + } + if (fields.length == 0) { + return data; + } + for (Map m : data) { + Map r = new HashMap<>(); + for (String field : fields) { + r.put(field, m.get(field)); + } + result.add(r); + } + return result; + } + +} diff --git a/fizz-core/src/main/java/we/fizz/function/MathFunc.java b/fizz-core/src/main/java/we/fizz/function/MathFunc.java new file mode 100644 index 0000000..b2bfa8d --- /dev/null +++ b/fizz-core/src/main/java/we/fizz/function/MathFunc.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.fizz.function; + +import java.math.BigDecimal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Math Functions + * + * @author Francis Dong + * + */ +public class MathFunc implements IFunc { + + private static final Logger LOGGER = LoggerFactory.getLogger(MathFunc.class); + + private static MathFunc singleton; + + public static MathFunc getInstance() { + if (singleton == null) { + synchronized (MathFunc.class) { + if (singleton == null) { + MathFunc instance = new MathFunc(); + instance.init(); + singleton = instance; + } + } + } + return singleton; + } + + private MathFunc() { + } + + public void init() { + FuncExecutor.register(NAME_SPACE_PREFIX + "math.absExact", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.negateExact", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.addExact", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.subtractExact", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.multiplyExact", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.maxExact", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.minExact", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.mod", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.pow", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.sqrt", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.random", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.absDecimal", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.negateDecimal", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.addDecimal", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.subtractDecimal", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.multiplyDecimal", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.divideDecimal", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.maxDecimal", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.minDecimal", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "math.scaleDecimal", this); + } + + public long absExact(long a) { + return Math.abs(a); + } + + public long negateExact(long a) { + return Math.negateExact(a); + } + + public long addExact(long x, long y) { + return Math.addExact(x, y); + } + + public long subtractExact(long x, long y) { + return Math.subtractExact(x, y); + } + + public long multiplyExact(long x, long y) { + return Math.multiplyExact(x, y); + } + + public long maxExact(long x, long y) { + return Math.max(x, y); + } + + public long minExact(long x, long y) { + return Math.min(x, y); + } + + public long mod(long x, long y) { + return Math.floorMod(x, y); + } + + public double pow(double a, double b) { + return Math.pow(a, b); + } + + public double sqrt(double a) { + return Math.sqrt(a); + } + + /** + * Returns a {@code double} value with a positive sign, greater than or equal to + * {@code 0.0} and less than {@code 1.0}. Returned values are chosen + * pseudorandomly with (approximately) uniform distribution from that range. + * + * @return + */ + public double random() { + return Math.random(); + } + + public double absDecimal(double a) { + return BigDecimal.valueOf(a).abs().doubleValue(); + } + + public double negateDecimal(double a) { + return BigDecimal.valueOf(a).negate().doubleValue(); + } + + public double addDecimal(double x, double y) { + return BigDecimal.valueOf(x).add(BigDecimal.valueOf(y)).doubleValue(); + } + + public double subtractDecimal(double x, double y) { + return BigDecimal.valueOf(x).subtract(BigDecimal.valueOf(y)).doubleValue(); + } + + public double multiplyDecimal(double x, double y) { + return BigDecimal.valueOf(x).multiply(BigDecimal.valueOf(y)).doubleValue(); + } + + public double divideDecimal(double x, double y) { + return BigDecimal.valueOf(x).divide(BigDecimal.valueOf(y)).doubleValue(); + } + + public double maxDecimal(double x, double y) { + return BigDecimal.valueOf(x).max(BigDecimal.valueOf(y)).doubleValue(); + } + + public double minDecimal(double x, double y) { + return BigDecimal.valueOf(x).min(BigDecimal.valueOf(y)).doubleValue(); + } + + public double scaleDecimal(double a, int scale) { + return BigDecimal.valueOf(a).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); + } + +} diff --git a/fizz-core/src/main/java/we/fizz/function/StringFunc.java b/fizz-core/src/main/java/we/fizz/function/StringFunc.java new file mode 100644 index 0000000..a0d328b --- /dev/null +++ b/fizz-core/src/main/java/we/fizz/function/StringFunc.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.fizz.function; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import we.fizz.exception.FizzRuntimeException; + +/** + * String Functions + * + * @author Francis Dong + * + */ +public class StringFunc implements IFunc { + + private static final Logger LOGGER = LoggerFactory.getLogger(StringFunc.class); + + private static StringFunc singleton; + + public static StringFunc getInstance() { + if (singleton == null) { + synchronized (StringFunc.class) { + if (singleton == null) { + StringFunc instance = new StringFunc(); + instance.init(); + singleton = instance; + } + } + } + return singleton; + } + + private StringFunc() { + } + + public void init() { + FuncExecutor.register(NAME_SPACE_PREFIX + "string.concat", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "string.concatws", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "string.substring", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "string.indexOf", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "string.startsWith", this); + FuncExecutor.register(NAME_SPACE_PREFIX + "string.endsWith", this); + } + + /** + * Concat strings + * + * @param strs + * @return + */ + public String concat(String... strs) { + return StringUtils.join(strs); + } + + /** + * Concat with separator + * + * @param strs + * @return + */ + public String concatws(String separator, String... strs) { + return StringUtils.join(strs, separator); + } + + /** + * Returns a string that is a substring of this string. The substring begins at + * the specified {@code beginIndex} and extends to the character at index + * {@code endIndex - 1}. Thus the length of the substring is + * {@code endIndex-beginIndex}. + * + * @param str + * @param beginIndex + * @param endIndex + * @return + */ + public String substring(String str, int beginIndex, int... endIndex) { + if (StringUtils.isBlank(str)) { + return str; + } + if (endIndex != null && endIndex.length > 0) { + if (endIndex.length > 1) { + LOGGER.error("invalid argument: endIndex"); + throw new FizzRuntimeException("invalid argument: endIndex"); + } + return str.substring(beginIndex, endIndex[0]); + } + return str.substring(beginIndex); + } + + /** + * Returns the index within this string of the first occurrence of the specified + * substring. + * + * @param str + * @param substr + * @return the index of the first occurrence of the specified substring, or + * {@code -1} if there is no such occurrence. + */ + public int indexOf(String str, String substr) { + return str.indexOf(substr); + } + + /** + * Tests if this string starts with the specified prefix. + * + * @param prefix the prefix. + * @return {@code true} if the character sequence represented by the argument is + * a prefix of the character sequence represented by this string; + * {@code false} otherwise. Note also that {@code true} will be returned + * if the argument is an empty string or is equal to this {@code String} + * object as determined by the {@link #equals(Object)} method. + */ + public boolean startsWith(String str, String prefix) { + return str.startsWith(prefix); + } + + /** + * Tests if this string starts with the specified prefix. + * + * @param prefix the prefix. + * @return {@code true} if the character sequence represented by the argument is + * a prefix of the character sequence represented by this string; + * {@code false} otherwise. Note also that {@code true} will be returned + * if the argument is an empty string or is equal to this {@code String} + * object as determined by the {@link #equals(Object)} method. + */ + public boolean endsWith(String str, String suffix) { + return str.endsWith(suffix); + } + +} diff --git a/fizz-core/src/main/java/we/fizz/input/PathMapping.java b/fizz-core/src/main/java/we/fizz/input/PathMapping.java index a280c98..b7e37de 100644 --- a/fizz-core/src/main/java/we/fizz/input/PathMapping.java +++ b/fizz-core/src/main/java/we/fizz/input/PathMapping.java @@ -27,6 +27,8 @@ import org.noear.snack.ONode; import we.constants.CommonConstants; import we.fizz.StepContext; import we.fizz.exception.FizzRuntimeException; +import we.fizz.function.FuncExecutor; +import we.fizz.function.IFunc; import we.util.MapUtil; /** @@ -194,25 +196,33 @@ public class PathMapping { private static Object getRefValue(ONode ctxNode, String type, String path) { Object obj = null; - try { - String p = path; - String defaultValue = null; - if (path.indexOf("|") != -1) { - p = path.substring(0, path.indexOf("|")); - defaultValue = path.substring(path.indexOf("|") + 1); - } - ONode val = select(ctxNode, handlePath(p)); - if (val != null && !val.isNull()) { - obj = val; - } else { - obj = defaultValue; - } + // check if it is a function + if (path.startsWith(IFunc.NAME_SPACE_PREFIX)) { + obj = FuncExecutor.getInstance().exec(ctxNode, path); if (obj != null && type != null) { obj = cast(obj, type); } - } catch (Exception e) { - e.printStackTrace(); - throw new FizzRuntimeException(String.format("path mapping errer: %s , path mapping data: %s %s", e.getMessage(), type, path), e); + } else { + try { + String p = path; + String defaultValue = null; + if (path.indexOf("|") != -1) { + p = path.substring(0, path.indexOf("|")); + defaultValue = path.substring(path.indexOf("|") + 1); + } + ONode val = select(ctxNode, handlePath(p)); + if (val != null && !val.isNull()) { + obj = val; + } else { + obj = defaultValue; + } + if (obj != null && type != null) { + obj = cast(obj, type); + } + } catch (Exception e) { + e.printStackTrace(); + throw new FizzRuntimeException(String.format("path mapping errer: %s , path mapping data: %s %s", e.getMessage(), type, path), e); + } } return obj; } @@ -259,6 +269,8 @@ public class PathMapping { case "string": { if (obj instanceof ONode) { obj = ((ONode) obj).val().getString(); + } else { + obj = String.valueOf(obj.toString()); } break; } diff --git a/fizz-core/src/test/java/we/fizz/function/CodecFuncTests.java b/fizz-core/src/test/java/we/fizz/function/CodecFuncTests.java new file mode 100644 index 0000000..862d595 --- /dev/null +++ b/fizz-core/src/test/java/we/fizz/function/CodecFuncTests.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.fizz.function; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.noear.snack.ONode; + +import we.fizz.input.PathMapping; +import we.util.DigestUtils; + +/** + * + * @author Francis Dong + * + */ +class CodecFuncTests { + @Test + void contextLoads() { + } + + + @Test + void testMd5() { + String funcExpression = "fn.codec.md5(\"abc\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("900150983cd24fb0d6963f7d28e17f72", result.toString()); + } + + @Test + void testMd5_2() { + String funcExpression = "fn.codec.md5(fn.date.add(fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, fn.math.addExact(999,1)), \"yyyy-MM-dd HH:mm:ss\", fn.math.addExact(0,1), 1000))"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(DigestUtils.md5Hex("2021-07-09 22:44:57"), result.toString()); + } + + @Test + void testSha1() { + String funcExpression = "fn.codec.sha1(\"abc\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("a9993e364706816aba3e25717850c26c9cd0d89d", result.toString()); + } + + @Test + void testSha256() { + String funcExpression = "fn.codec.sha256(\"abc\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", result.toString()); + } + + @Test + void testSha384() { + String funcExpression = "fn.codec.sha384(\"abc\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7", result.toString()); + } + + @Test + void testSha512() { + String funcExpression = "fn.codec.sha512(\"abc\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", result.toString()); + } + + @Test + void testBase64Encode() { + String funcExpression = "fn.codec.base64Encode(\"Base64编码介绍\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("QmFzZTY057yW56CB5LuL57uN", result.toString()); + } + + @Test + void testBase64Decode() { + String funcExpression = "fn.codec.base64Decode(\"QmFzZTY057yW56CB5LuL57uN\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("Base64编码介绍", result.toString()); + } + + @Test + void testAesEncrypt() { + String funcExpression = "fn.codec.aesEncrypt(\"abc\", \"12345678123456781234567812345678\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("MQn0lgH5W6tS6Ii2c7UeSg==", result.toString()); + } + + @Test + void testAesDecrypt() { + String funcExpression = "fn.codec.aesDecrypt(\"MQn0lgH5W6tS6Ii2c7UeSg==\", \"12345678123456781234567812345678\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("abc", result.toString()); + } + + @Test + void testDesEncrypt() { + String funcExpression = "fn.codec.desEncrypt(\"abc\", \"12345678123456781234567812345678\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("9YR6ZPdZufM=", result.toString()); + } + + @Test + void testDesDecrypt() { + String funcExpression = "fn.codec.desDecrypt(\"9YR6ZPdZufM=\", \"12345678123456781234567812345678\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("abc", result.toString()); + } + +} \ No newline at end of file diff --git a/fizz-core/src/test/java/we/fizz/function/DateFuncTests.java b/fizz-core/src/test/java/we/fizz/function/DateFuncTests.java new file mode 100644 index 0000000..d3dcd0f --- /dev/null +++ b/fizz-core/src/test/java/we/fizz/function/DateFuncTests.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.fizz.function; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * + * @author Francis Dong + * + */ +class DateFuncTests { + @Test + void contextLoads() { + } + + @Test + void testExec() { + String funcExpression = "fn.date.timestamp()"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + System.out.println(result); + } + + @Test + void testAdd() { + String funcExpression = "fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, 1000)"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-07-09 22:44:56", result.toString()); + } + + @Test + void testEmbeddedAdd() { + String funcExpression = "fn.date.add(fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, 1000), \"yyyy-MM-dd HH:mm:ss\", 1, 1000)"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-07-09 22:44:57", result.toString()); + } + + + @Test + void testFormatTs() { + String funcExpression = "fn.date.formatTs(1628825352227, \"yyyy-MM-dd HH:mm:ss\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-08-13 11:29:12", result.toString()); + } + + @Test + void testChangePattern() { + String funcExpression = "fn.date.changePattern(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", \"MM-dd HH:mm\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("07-09 22:44", result.toString()); + } + +} \ No newline at end of file diff --git a/fizz-core/src/test/java/we/fizz/function/ListFuncTests.java b/fizz-core/src/test/java/we/fizz/function/ListFuncTests.java new file mode 100644 index 0000000..c2c9853 --- /dev/null +++ b/fizz-core/src/test/java/we/fizz/function/ListFuncTests.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.fizz.function; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.noear.snack.ONode; + +import we.fizz.input.PathMapping; + +/** + * + * @author Francis Dong + * + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +class ListFuncTests { + @Test + void contextLoads() { + } + + private Map createRecord(String key, Object value) { + Map m = new HashMap<>(); + m.put(key, value); + return m; + } + + private Map createRecord2(int index) { + Map m = new HashMap<>(); + m.put("a", "a" + index); + m.put("b", "b" + index); + m.put("c", "c" + index); + m.put("d", "d" + index); + m.put("e", "e" + index); + return m; + } + + @Test + void testExpand() { + List> data = new ArrayList<>(); + + List subList1 = new ArrayList<>(); + subList1.add(createRecord("a", "a1")); + subList1.add(createRecord("a", "a2")); + subList1.add(createRecord("a", "a3")); + + List subList2 = new ArrayList<>(); + subList2.add(createRecord("a", "a4")); + subList2.add(createRecord("a", "a5")); + subList2.add(createRecord("a", "a6")); + + data.add(subList1); + data.add(subList2); + + ONode ctxNode = ONode.load(new HashMap()); + PathMapping.setByPath(ctxNode, "test.data", data, true); + + String funcExpression = "fn.list.expand({test.data})"; + List result = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression); + assertEquals(6, result.size()); + assertEquals("a2", ((Map) result.get(1)).get("a").toString()); + assertEquals("a4", ((Map) result.get(3)).get("a").toString()); + } + + + @Test + void testMerge() { + List subList1 = new ArrayList<>(); + subList1.add(createRecord("a", "a1")); + subList1.add(createRecord("a", "a2")); + subList1.add(createRecord("a", "a3")); + + List subList2 = new ArrayList<>(); + subList2.add(createRecord("a", "a4")); + subList2.add(createRecord("a", "a5")); + subList2.add(createRecord("a", "a6")); + + + ONode ctxNode = ONode.load(new HashMap()); + PathMapping.setByPath(ctxNode, "test.data1", subList1, true); + PathMapping.setByPath(ctxNode, "test.data2", subList2, true); + + String funcExpression = "fn.list.merge({test.data1}, {test.data2})"; + List result = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression); + assertEquals(6, result.size()); + assertEquals("a2", ((Map) result.get(1)).get("a").toString()); + assertEquals("a4", ((Map) result.get(3)).get("a").toString()); + } + + @Test + void testExtract() { + List subList1 = new ArrayList<>(); + subList1.add(createRecord2(1)); + subList1.add(createRecord2(2)); + subList1.add(createRecord2(3)); + subList1.add(createRecord2(4)); + subList1.add(createRecord2(5)); + + + ONode ctxNode = ONode.load(new HashMap()); + PathMapping.setByPath(ctxNode, "test.data", subList1, true); + + String funcExpression = "fn.list.extract({test.data}, \"c\",\"b\", \"e\")"; + List result = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression); + assertEquals(5, result.size()); + assertEquals("c2", ((Map) result.get(1)).get("c").toString()); + assertEquals("e4", ((Map) result.get(3)).get("e").toString()); + assertEquals(null, ((Map) result.get(3)).get("a")); + assertEquals(null, ((Map) result.get(3)).get("d")); +// System.out.println(result); + } + +} \ No newline at end of file diff --git a/fizz-core/src/test/java/we/fizz/function/MathFuncTests.java b/fizz-core/src/test/java/we/fizz/function/MathFuncTests.java new file mode 100644 index 0000000..1545c92 --- /dev/null +++ b/fizz-core/src/test/java/we/fizz/function/MathFuncTests.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.fizz.function; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.noear.snack.ONode; + +import we.fizz.input.PathMapping; + +/** + * + * @author Francis Dong + * + */ +class MathFuncTests { + @Test + void contextLoads() { + } + + @Test + void testAbsExact() { + String funcExpression = "fn.math.absExact(-3)"; + long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(3, result); + } + + @Test + void testNegateExact() { + String funcExpression = "fn.math.negateExact(4)"; + long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(-4, result); + } + + @Test + void testNegateExact2() { + String funcExpression = "fn.math.negateExact(-4)"; + long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(4, result); + } + + @Test + void testAddExact() { + String funcExpression = "fn.math.addExact(14,-1)"; + long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(13, result); + } + + @Test + void testSubtractExact() { + String funcExpression = "fn.math.subtractExact(14,-1)"; + long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(15, result); + } + + @Test + void testMultiplyExact() { + String funcExpression = "fn.math.multiplyExact(14,2)"; + long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(28, result); + } + + @Test + void testMaxExact() { + String funcExpression = "fn.math.maxExact(14,2)"; + long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(14, result); + } + + @Test + void testMinExact() { + String funcExpression = "fn.math.minExact(14,2)"; + long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(2, result); + } + + @Test + void testMod() { + String funcExpression = "fn.math.mod(13,2)"; + long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(1, result); + } + + @Test + void testPow() { + String funcExpression = "fn.math.pow(2,3)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(8, result); + } + + @Test + void testSqrt() { + String funcExpression = "fn.math.sqrt(4)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(2, result); + } + + @Test + void testAbsDecimal() { + String funcExpression = "fn.math.absDecimal(-4)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(4, result); + } + + @Test + void testNegateDecimal() { + String funcExpression = "fn.math.negateDecimal(4)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(-4, result); + } + + @Test + void testSubtractDecimal() { + String funcExpression = "fn.math.subtractDecimal(4,1.3)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(2.7, result); + } + + @Test + void testMultiplyDecimal() { + String funcExpression = "fn.math.multiplyDecimal(4,2.2)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(8.8, result); + } + + @Test + void testDivideDecimal() { + String funcExpression = "fn.math.divideDecimal(4.8,2)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(2.4, result); + } + + @Test + void testMaxDecimal() { + String funcExpression = "fn.math.maxDecimal(4.8,2)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(4.8, result); + } + + @Test + void testMinDecimal() { + String funcExpression = "fn.math.minDecimal(4.8,2)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(2, result); + } + + @Test + void testScaleDecimal() { + String funcExpression = "fn.math.scaleDecimal(4.8456,2)"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals(4.85, result); + } + + @Test + void testRandom() { + String funcExpression = "fn.math.random()"; + double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); + // System.out.println(result); + } + +} \ No newline at end of file diff --git a/fizz-core/src/test/java/we/fizz/function/StringFuncTests.java b/fizz-core/src/test/java/we/fizz/function/StringFuncTests.java new file mode 100644 index 0000000..4c342d8 --- /dev/null +++ b/fizz-core/src/test/java/we/fizz/function/StringFuncTests.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package we.fizz.function; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.noear.snack.ONode; + +import we.fizz.input.PathMapping; + +/** + * + * @author Francis Dong + * + */ +class StringFuncTests { + @Test + void contextLoads() { + } + + + @Test + void testConcat() { + String funcExpression = "fn.string.concat(\"2021-07-09 22:44:55\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-07-09 22:44:55", result.toString()); + } + + + @Test + void testConcat2() { + String funcExpression = "fn.string.concat(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-07-09 22:44:55yyyy-MM-dd HH:mm:ss", result.toString()); + } + + @Test + void testConcatws() { + String funcExpression = "fn.string.concatws(\",\" , \"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\")"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-07-09 22:44:55,yyyy-MM-dd HH:mm:ss", result.toString()); + } + + @Test + void testSubstring() { + String funcExpression = "fn.string.substring(\"2021-07-09 22:44:55\", 1 , 4)"; + Object result = FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-07-09 22:44:55".substring(1, 4), result.toString()); + } + + @Test + void testSubstring2() { + ONode ctxNode = ONode.load(new HashMap()); + + Map m = new HashMap<>(); + m.put("a", "1"); + m.put("b", "1"); + + PathMapping.setByPath(ctxNode, "data.dateStr", "2021-07-09 22:44:55", true); + PathMapping.setByPath(ctxNode, "data.startIndex", 1, true); + PathMapping.setByPath(ctxNode, "data", m, false); + + String funcExpression = "fn.string.substring({data.dateStr}, {data.startIndex})"; +// String funcExpression = "fn.string.substring(\"2021-07-09 22:44:55\", 1)"; + Object result = FuncExecutor.getInstance().exec(ctxNode, funcExpression); + assertEquals("2021-07-09 22:44:55".substring(1), result.toString()); + } + + @Test + void testIndexOf() { + String funcExpression = "fn.string.indexOf(\"2021-07-09 22:44:55\", \"07\")"; + int result = (int)FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-07-09 22:44:55".indexOf("07"), result); + } + + @Test + void testStartsWith() { + String funcExpression = "fn.string.startsWith(\"2021-07-09 22:44:55\", \"2021\")"; + boolean result = (boolean)FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-07-09 22:44:55".startsWith("2021"), result); + } + + @Test + void testEndsWith() { + String funcExpression = "fn.string.endsWith(\"2021-07-09 22:44:55\", \"44:55\")"; + boolean result = (boolean)FuncExecutor.getInstance().exec(null, funcExpression); + assertEquals("2021-07-09 22:44:55".endsWith("44:55"), result); + } + + + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2748f36..aee0cd1 100644 --- a/pom.xml +++ b/pom.xml @@ -370,6 +370,12 @@ commons-codec 1.15 + + + commons-beanutils + commons-beanutils + 1.9.4 +