Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flink.table.runtime.functions; import org.apache.flink.table.dataformat.BinaryString; import org.apache.flink.table.dataformat.Decimal; import org.apache.flink.table.runtime.util.JsonUtils; import org.apache.flink.table.utils.EncodingUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Built-in scalar runtime functions. * * <p>NOTE: Before you add functions here, check if Calcite provides it in * {@code org.apache.calcite.runtime.SqlFunctions}. Furthermore, make sure * to implement the function efficiently. Sometimes it makes sense to create a * {@code org.apache.flink.table.codegen.calls.CallGenerator} instead to avoid * massive object creation and reuse instances. */ public class SqlFunctionUtils { private static final Logger LOG = LoggerFactory.getLogger(SqlFunctionUtils.class); private static final ThreadLocalCache<String, Pattern> REGEXP_PATTERN_CACHE = new ThreadLocalCache<String, Pattern>() { @Override public Pattern getNewInstance(String regex) { return Pattern.compile(regex); } }; private static final ThreadLocalCache<String, URL> URL_CACHE = new ThreadLocalCache<String, URL>() { public URL getNewInstance(String url) { try { return new URL(url); } catch (MalformedURLException e) { throw new RuntimeException(e); } } }; private static final Map<String, String> EMPTY_MAP = new HashMap<>(0); public static double exp(Decimal d) { return Math.exp(d.doubleValue()); } public static double power(double base, Decimal exponent) { return Math.pow(base, exponent.doubleValue()); } public static double power(Decimal base, Decimal exponent) { return Math.pow(base.doubleValue(), exponent.doubleValue()); } public static double power(Decimal base, double exponent) { return Math.pow(base.doubleValue(), exponent); } public static double cosh(Decimal x) { return Math.cosh(x.doubleValue()); } public static double acos(Decimal a) { return Math.acos(a.doubleValue()); } public static double asin(Decimal a) { return Math.asin(a.doubleValue()); } public static double atan(Decimal a) { return Math.atan(a.doubleValue()); } public static double atan2(Decimal y, Decimal x) { return Math.atan2(y.doubleValue(), x.doubleValue()); } public static double sin(Decimal a) { return Math.sin(a.doubleValue()); } public static double sinh(Decimal a) { return Math.sinh(a.doubleValue()); } public static double cos(Decimal a) { return Math.cos(a.doubleValue()); } public static double tan(Decimal a) { return Math.tan(a.doubleValue()); } /** * Calculates the hyperbolic tangent of a big decimal number. */ public static double tanh(Decimal a) { return Math.tanh(a.doubleValue()); } public static double cot(Decimal a) { return 1.0d / Math.tan(a.doubleValue()); } public static double degrees(Decimal angrad) { return Math.toDegrees(angrad.doubleValue()); } public static double radians(Decimal angdeg) { return Math.toRadians(angdeg.doubleValue()); } public static Decimal abs(Decimal a) { return a.abs(); } public static Decimal floor(Decimal a) { return a.floor(); } public static Decimal ceil(Decimal a) { return a.ceil(); } // -------------------------- natural logarithm ------------------------ /** * Returns the natural logarithm of "x". */ public static double log(double x) { return Math.log(x); } public static double log(Decimal x) { return Math.log(x.doubleValue()); } /** * Returns the logarithm of "x" with base "base". */ public static double log(double base, double x) { return Math.log(x) / Math.log(base); } public static double log(double base, Decimal x) { return log(base, x.doubleValue()); } public static double log(Decimal base, double x) { return log(base.doubleValue(), x); } public static double log(Decimal base, Decimal x) { return log(base.doubleValue(), x.doubleValue()); } /** * Returns the logarithm of "a" with base 2. */ public static double log2(double x) { return Math.log(x) / Math.log(2); } public static double log2(Decimal x) { return log2(x.doubleValue()); } public static double log10(double x) { return Math.log10(x); } public static double log10(Decimal x) { return log10(x.doubleValue()); } // -------------------------- string functions ------------------------ /** * Returns the string str left-padded with the string pad to a length of len characters. * If str is longer than len, the return value is shortened to len characters. */ public static String lpad(String base, int len, String pad) { if (len < 0 || "".equals(pad)) { return null; } else if (len == 0) { return ""; } char[] data = new char[len]; char[] baseChars = base.toCharArray(); char[] padChars = pad.toCharArray(); // the length of the padding needed int pos = Math.max(len - base.length(), 0); // copy the padding for (int i = 0; i < pos; i += pad.length()) { for (int j = 0; j < pad.length() && j < pos - i; j++) { data[i + j] = padChars[j]; } } // copy the base int i = 0; while (pos + i < len && i < base.length()) { data[pos + i] = baseChars[i]; i += 1; } return new String(data); } /** * Returns the string str right-padded with the string pad to a length of len characters. * If str is longer than len, the return value is shortened to len characters. */ public static String rpad(String base, int len, String pad) { if (len < 0 || "".equals(pad)) { return null; } else if (len == 0) { return ""; } char[] data = new char[len]; char[] baseChars = base.toCharArray(); char[] padChars = pad.toCharArray(); int pos = 0; // copy the base while (pos < base.length() && pos < len) { data[pos] = baseChars[pos]; pos += 1; } // copy the padding while (pos < len) { int i = 0; while (i < pad.length() && i < len - pos) { data[pos + i] = padChars[i]; i += 1; } pos += pad.length(); } return new String(data); } /** * Returns a string that repeats the base string n times. */ public static String repeat(String str, int repeat) { return EncodingUtils.repeat(str, repeat); } /** * Replaces all the old strings with the replacement string. */ public static String replace(String str, String oldStr, String replacement) { return str.replace(oldStr, replacement); } /** * Split target string with custom separator and pick the index-th(start with 0) result. * * @param str target string. * @param separator custom separator. * @param index index of the result which you want. * @return the string at the index of split results. */ public static String splitIndex(String str, String separator, int index) { if (index < 0) { return null; } String[] values = StringUtils.splitByWholeSeparatorPreserveAllTokens(str, separator); if (index >= values.length) { return null; } else { return values[index]; } } /** * Split target string with custom separator and pick the index-th(start with 0) result. * * @param str target string. * @param character int value of the separator character * @param index index of the result which you want. * @return the string at the index of split results. */ public static String splitIndex(String str, int character, int index) { if (character > 255 || character < 1 || index < 0) { return null; } String[] values = StringUtils.splitPreserveAllTokens(str, (char) character); if (index >= values.length) { return null; } else { return values[index]; } } /** * Returns a string resulting from replacing all substrings that match the regular * expression with replacement. */ public static String regexpReplace(String str, String regex, String replacement) { if (regex.isEmpty()) { return str; } try { // we should use StringBuffer here because Matcher only accept it StringBuffer sb = new StringBuffer(); Matcher m = REGEXP_PATTERN_CACHE.get(regex).matcher(str); while (m.find()) { m.appendReplacement(sb, replacement); } m.appendTail(sb); return sb.toString(); } catch (Exception e) { LOG.error(String.format("Exception in regexpReplace('%s', '%s', '%s')", str, regex, replacement), e); // return null if exception in regex replace return null; } } /** * Returns a string extracted with a specified regular expression and a regex * match group index. */ public static String regexpExtract(String str, String regex, int extractIndex) { if (extractIndex < 0) { return null; } try { Matcher m = REGEXP_PATTERN_CACHE.get(regex).matcher(str); if (m.find()) { MatchResult mr = m.toMatchResult(); return mr.group(extractIndex); } return null; } catch (Exception e) { LOG.error(String.format("Exception in regexpExtract('%s', '%s', '%d')", str, regex, extractIndex), e); return null; } } public static String regexpExtract(String str, String regex, long extractIndex) { return regexpExtract(str, regex, (int) extractIndex); } /** * Returns the first string extracted with a specified regular expression. */ public static String regexpExtract(String str, String regex) { return regexpExtract(str, regex, 0); } /** * Parse string as key-value string and return the value matches key name. * example: * keyvalue('k1=v1;k2=v2', ';', '=', 'k2') = 'v2' * keyvalue('k1:v1,k2:v2', ',', ':', 'k3') = NULL * * @param str target string. * @param pairSeparator separator between key-value tuple. * @param kvSeparator separator between key and value. * @param keyName name of the key whose value you want return. * @return target value. */ public static BinaryString keyValue(BinaryString str, BinaryString pairSeparator, BinaryString kvSeparator, BinaryString keyName) { if (str == null || str.getSizeInBytes() == 0) { return null; } if (pairSeparator != null && pairSeparator.getSizeInBytes() == 1 && kvSeparator != null && kvSeparator.getSizeInBytes() == 1) { return str.keyValue(pairSeparator.getByte(0), kvSeparator.getByte(0), keyName); } else { return BinaryString .fromString(keyValue(BinaryString.safeToString(str), BinaryString.safeToString(pairSeparator), BinaryString.safeToString(kvSeparator), BinaryString.safeToString(keyName))); } } private static String keyValue(String str, String pairSeparator, String kvSeparator, String keyName) { try { if (StringUtils.isEmpty(str)) { return null; } String[] values = StringUtils.split(str, pairSeparator); for (String value : values) { if (!StringUtils.isEmpty(value)) { String[] kv = StringUtils.split(kvSeparator); if (kv != null && kv.length == 2 && kv[0].equals(keyName)) { return kv[1]; } } } return null; } catch (Exception e) { LOG.error("Exception when parse key-value", e); return null; } } /** * Calculate the hash value of a given string. * * @param algorithm message digest algorithm. * @param str string to hash. * @return hash value of string. */ public static String hash(String algorithm, String str) { return hash(algorithm, str, ""); } /** * Calculate the hash value of a given string. * * @param algorithm message digest algorithm. * @param str string to hash. * @param charsetName charset of string. * @return hash value of string. */ public static String hash(String algorithm, String str, String charsetName) { try { byte[] digest = MessageDigest.getInstance(algorithm).digest(strToBytesWithCharset(str, charsetName)); return EncodingUtils.hex(digest); } catch (NoSuchAlgorithmException e) { throw new IllegalArgumentException("Unsupported algorithm: " + algorithm, e); } } private static byte[] strToBytesWithCharset(String str, String charsetName) { byte[] byteArray = null; if (!StringUtils.isEmpty(charsetName)) { try { byteArray = str.getBytes(charsetName); } catch (UnsupportedEncodingException e) { LOG.warn("Unsupported encoding: " + charsetName + ", fallback to system charset", e); byteArray = null; } } if (byteArray == null) { byteArray = str.getBytes(); } return byteArray; } /** * Parse url and return various components of the URL. * If accept any null arguments, return null. * * @param urlStr URL string. * @param partToExtract determines which components would return. * accept values: * HOST,PATH,QUERY,REF, * PROTOCOL,FILE,AUTHORITY,USERINFO * @return target value. */ public static String parseUrl(String urlStr, String partToExtract) { URL url; try { url = URL_CACHE.get(urlStr); } catch (Exception e) { LOG.error("Parse URL error: " + urlStr, e); return null; } if ("HOST".equals(partToExtract)) { return url.getHost(); } if ("PATH".equals(partToExtract)) { return url.getPath(); } if ("QUERY".equals(partToExtract)) { return url.getQuery(); } if ("REF".equals(partToExtract)) { return url.getRef(); } if ("PROTOCOL".equals(partToExtract)) { return url.getProtocol(); } if ("FILE".equals(partToExtract)) { return url.getFile(); } if ("AUTHORITY".equals(partToExtract)) { return url.getAuthority(); } if ("USERINFO".equals(partToExtract)) { return url.getUserInfo(); } return null; } /** * Parse url and return various parameter of the URL. * If accept any null arguments, return null. * * @param urlStr URL string. * @param partToExtract must be QUERY, or return null. * @param key parameter name. * @return target value. */ public static String parseUrl(String urlStr, String partToExtract, String key) { if (!"QUERY".equals(partToExtract)) { return null; } String query = parseUrl(urlStr, partToExtract); if (query == null) { return null; } Pattern p = Pattern.compile("(&|^)" + Pattern.quote(key) + "=([^&]*)"); Matcher m = p.matcher(query); if (m.find()) { return m.group(2); } return null; } public static int divideInt(int a, int b) { return a / b; } public static String subString(String str, long start, long len) { if (len < 0) { LOG.error("len of 'substring(str, start, len)' must be >= 0 and Int type, but len = {0}", len); return null; } if (len > Integer.MAX_VALUE || start > Integer.MAX_VALUE) { LOG.error("len or start of 'substring(str, start, len)' must be Int type, but len = {0}, start = {0}", len, start); return null; } int length = (int) len; int pos = (int) start; if (str.isEmpty()) { return ""; } int startPos; int endPos; if (pos > 0) { startPos = pos - 1; if (startPos >= str.length()) { return ""; } } else if (pos < 0) { startPos = str.length() + pos; if (startPos < 0) { return ""; } } else { startPos = 0; } if ((str.length() - startPos) < length) { endPos = str.length(); } else { endPos = startPos + length; } return str.substring(startPos, endPos); } public static String subString(String str, long start) { return subString(str, start, Integer.MAX_VALUE); } public static String chr(long chr) { if (chr < 0) { return ""; } else if ((chr & 0xFF) == 0) { return String.valueOf(Character.MIN_VALUE); } else { return String.valueOf((char) (chr & 0xFF)); } } public static String overlay(String s, String r, long start, long length) { if (start <= 0 || start > s.length()) { return s; } else { StringBuilder sb = new StringBuilder(); int startPos = (int) start; int len = (int) length; sb.append(s, 0, startPos - 1); sb.append(r); if ((startPos + len) <= s.length() && len > 0) { sb.append(s.substring(startPos - 1 + len)); } return sb.toString(); } } public static String overlay(String s, String r, long start) { return overlay(s, r, start, r.length()); } public static int position(BinaryString seek, BinaryString s) { return position(seek, s, 1); } public static int position(BinaryString seek, BinaryString s, int from) { return s.indexOf(seek, from - 1) + 1; } public static int instr(BinaryString str, BinaryString subString, int startPosition, int nthAppearance) { if (nthAppearance <= 0) { throw new IllegalArgumentException("nthAppearance must be positive!"); } if (startPosition == 0) { return 0; } else if (startPosition > 0) { int startIndex = startPosition; int index = 0; for (int i = 0; i < nthAppearance; i++) { index = str.indexOf(subString, startIndex - 1) + 1; if (index == 0) { return 0; } startIndex = index + 1; } return index; } else { int pos = instr(str.reverse(), subString.reverse(), -startPosition, nthAppearance); if (pos == 0) { return 0; } else { return str.numChars() + 2 - pos - subString.numChars(); } } } /** * Returns the hex string of a long argument. */ public static String hex(long x) { return Long.toHexString(x).toUpperCase(); } /** * Returns the hex string of a string argument. */ public static String hex(String x) { return EncodingUtils.hex(x.getBytes(StandardCharsets.UTF_8)).toUpperCase(); } /** * Creates a map by parsing text. Split text into key-value pairs * using two delimiters. The first delimiter separates pairs, and the * second delimiter separates key and value. If only one parameter is given, * default delimiters are used: ',' as delimiter1 and '=' as delimiter2. * @param text the input text * @return the map */ public static Map<String, String> strToMap(String text) { return strToMap(text, ",", "="); } /** * Creates a map by parsing text. Split text into key-value pairs * using two delimiters. The first delimiter separates pairs, and the * second delimiter separates key and value. * @param text the input text * @param listDelimiter the delimiter to separates pairs * @param keyValueDelimiter the delimiter to separates key and value * @return the map */ public static Map<String, String> strToMap(String text, String listDelimiter, String keyValueDelimiter) { if (StringUtils.isEmpty(text)) { return EMPTY_MAP; } String[] keyValuePairs = text.split(listDelimiter); Map<String, String> ret = new HashMap<>(keyValuePairs.length); for (String keyValuePair : keyValuePairs) { String[] keyValue = keyValuePair.split(keyValueDelimiter, 2); if (keyValue.length < 2) { ret.put(keyValuePair, null); } else { ret.put(keyValue[0], keyValue[1]); } } return ret; } public static String jsonValue(String jsonString, String pathString) { // TODO: refactor this to use jackson ? return JsonUtils.getInstance().getJsonObject(jsonString, pathString); } // SQL ROUND /** SQL <code>ROUND</code> operator applied to int values. */ public static int sround(int b0) { return sround(b0, 0); } /** SQL <code>ROUND</code> operator applied to int values. */ public static int sround(int b0, int b1) { return sround(BigDecimal.valueOf(b0), b1).intValue(); } /** SQL <code>ROUND</code> operator applied to long values. */ public static long sround(long b0) { return sround(b0, 0); } /** SQL <code>ROUND</code> operator applied to long values. */ public static long sround(long b0, int b1) { return sround(BigDecimal.valueOf(b0), b1).longValue(); } /** SQL <code>ROUND</code> operator applied to BigDecimal values. */ public static BigDecimal sround(BigDecimal b0) { return sround(b0, 0); } /** SQL <code>ROUND</code> operator applied to BigDecimal values. */ public static BigDecimal sround(BigDecimal b0, int b1) { return b0.movePointRight(b1).setScale(0, RoundingMode.HALF_UP).movePointLeft(b1); } /** SQL <code>ROUND</code> operator applied to double values. */ public static double sround(double b0) { return sround(b0, 0); } /** SQL <code>ROUND</code> operator applied to double values. */ public static double sround(double b0, int b1) { return sround(BigDecimal.valueOf(b0), b1).doubleValue(); } /** SQL <code>ROUND</code> operator applied to Decimal values. */ public static Decimal sround(Decimal b0) { return sround(b0, 0); } /** SQL <code>ROUND</code> operator applied to Decimal values. */ public static Decimal sround(Decimal b0, int b1) { return Decimal.sround(b0, b1); } public static Decimal sign(Decimal b0) { return Decimal.sign(b0); } public static boolean isDecimal(Object obj) { if ((obj instanceof Long) || (obj instanceof Integer) || (obj instanceof Short) || (obj instanceof Byte) || (obj instanceof Float) || (obj instanceof Double) || (obj instanceof BigDecimal) || (obj instanceof BigInteger)) { return true; } if (obj instanceof String || obj instanceof Character) { String s = obj.toString(); if (s.isEmpty()) { return false; } return isInteger(s) || isLong(s) || isDouble(s); } else { return false; } } private static boolean isInteger(String s) { boolean flag = true; try { Integer.parseInt(s); } catch (NumberFormatException e) { flag = false; } return flag; } private static boolean isLong(String s) { boolean flag = true; try { Long.parseLong(s); } catch (NumberFormatException e) { flag = false; } return flag; } private static boolean isDouble(String s) { boolean flag = true; try { Double.parseDouble(s); } catch (NumberFormatException e) { flag = false; } return flag; } public static boolean isDigit(Object obj) { if ((obj instanceof Long) || (obj instanceof Integer) || (obj instanceof Short) || (obj instanceof Byte)) { return true; } if (obj instanceof String) { String s = obj.toString(); if (s.isEmpty()) { return false; } return StringUtils.isNumeric(s); } else { return false; } } public static boolean isAlpha(Object obj) { if (obj == null) { return false; } if (!(obj instanceof String)) { return false; } String s = obj.toString(); if ("".equals(s)) { return false; } return StringUtils.isAlpha(s); } public static Integer hashCode(String str) { if (str == null) { return Integer.MIN_VALUE; } return Math.abs(str.hashCode()); } public static Boolean regExp(String s, String regex) { if (regex.length() == 0) { return false; } try { return (REGEXP_PATTERN_CACHE.get(regex)).matcher(s).find(0); } catch (Exception e) { LOG.error("Exception when compile and match regex:" + regex + " on: " + s, e); return false; } } public static Byte bitAnd(Byte a, Byte b) { if (a == null || b == null) { return 0; } return (byte) (a & b); } public static Short bitAnd(Short a, Short b) { if (a == null || b == null) { return 0; } return (short) (a & b); } public static Integer bitAnd(Integer a, Integer b) { if (a == null || b == null) { return 0; } return a & b; } public static Long bitAnd(Long a, Long b) { if (a == null || b == null) { return 0L; } return a & b; } public static Byte bitNot(Byte a) { if (a == null) { a = 0; } return (byte) (~a); } public static Short bitNot(Short a) { if (a == null) { a = 0; } return (short) (~a); } public static Integer bitNot(Integer a) { if (a == null) { a = 0; } return ~a; } public static Long bitNot(Long a) { if (a == null) { a = 0L; } return ~a; } public static Byte bitOr(Byte a, Byte b) { if (a == null || b == null) { if (a == null) { a = 0; } if (b == null) { b = 0; } } return (byte) (a | b); } public static Short bitOr(Short a, Short b) { if (a == null || b == null) { if (a == null) { a = 0; } if (b == null) { b = 0; } } return (short) (a | b); } public static Integer bitOr(Integer a, Integer b) { if (a == null || b == null) { if (a == null) { a = 0; } if (b == null) { b = 0; } } return a | b; } public static Long bitOr(Long a, Long b) { if (a == null || b == null) { if (a == null) { a = 0L; } if (b == null) { b = 0L; } } return a | b; } public static Byte bitXor(Byte a, Byte b) { if (a == null || b == null) { if (a == null) { a = 0; } if (b == null) { b = 0; } } return (byte) (a ^ b); } public static Short bitXor(Short a, Short b) { if (a == null || b == null) { if (a == null) { a = 0; } if (b == null) { b = 0; } } return (short) (a ^ b); } public static Integer bitXor(Integer a, Integer b) { if (a == null || b == null) { if (a == null) { a = 0; } if (b == null) { b = 0; } } return a ^ b; } public static Long bitXor(Long a, Long b) { if (a == null || b == null) { if (a == null) { a = 0L; } if (b == null) { b = 0L; } } return a ^ b; } public static String toBase64(BinaryString bs) { return toBase64(bs.getBytes()); } public static String toBase64(byte[] bytes) { return Base64.getEncoder().encodeToString(bytes); } public static byte[] fromBase64(BinaryString bs) { return Base64.getDecoder().decode(bs.getBytes()); } public static String uuid() { return UUID.randomUUID().toString(); } public static String uuid(byte[] b) { return UUID.nameUUIDFromBytes(b).toString(); } }