| /* |
| * 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.openjpa.kernel; |
| |
| import java.lang.reflect.Method; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.sql.Time; |
| import java.sql.Timestamp; |
| import java.time.LocalDate; |
| import java.time.LocalDateTime; |
| import java.time.LocalTime; |
| import java.time.OffsetDateTime; |
| import java.time.OffsetTime; |
| import java.time.ZoneId; |
| import java.time.temporal.Temporal; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.List; |
| |
| import org.apache.openjpa.enhance.Reflection; |
| import org.apache.openjpa.kernel.exps.AggregateListener; |
| import org.apache.openjpa.kernel.exps.FilterListener; |
| import org.apache.openjpa.lib.util.J2DoPrivHelper; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.lib.util.StringUtil; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.util.InternalException; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * Helper methods for dealing with query filters. |
| * |
| * @author Abe White |
| */ |
| public class Filters { |
| |
| private static final BigDecimal ZERO_BIGDECIMAL = new BigDecimal(0D); |
| private static final BigInteger ZERO_BIGINTEGER = new BigInteger("0"); |
| |
| private static final int OP_ADD = 0; |
| private static final int OP_SUBTRACT = 1; |
| private static final int OP_MULTIPLY = 2; |
| private static final int OP_DIVIDE = 3; |
| private static final int OP_MOD = 4; |
| |
| private static final Localizer _loc = Localizer.forPackage(Filters.class); |
| |
| /** |
| * Return the correct wrapper type for the given class. |
| */ |
| public static Class<?> wrap(Class<?> c) { |
| if (!c.isPrimitive()) |
| return c; |
| if (c == int.class) |
| return Integer.class; |
| if (c == float.class) |
| return Float.class; |
| if (c == double.class) |
| return Double.class; |
| if (c == long.class) |
| return Long.class; |
| if (c == boolean.class) |
| return Boolean.class; |
| if (c == short.class) |
| return Short.class; |
| if (c == byte.class) |
| return Byte.class; |
| if (c == char.class) |
| return Character.class; |
| return c; |
| } |
| |
| /** |
| * Return the correct primitive type for the given class, if it is a |
| * wrapper. |
| */ |
| public static Class<?> unwrap(Class<?> c) { |
| if (c.isPrimitive() || c == String.class) |
| return c; |
| if (c == Integer.class) |
| return int.class; |
| if (c == Float.class) |
| return float.class; |
| if (c == Double.class) |
| return double.class; |
| if (c == Long.class) |
| return long.class; |
| if (c == Boolean.class) |
| return boolean.class; |
| if (c == Short.class) |
| return short.class; |
| if (c == Byte.class) |
| return byte.class; |
| if (c == Character.class) |
| return char.class; |
| return c; |
| } |
| |
| /** |
| * Given two types, return type they should both be converted |
| * to before performing any operations between them. |
| */ |
| public static Class<?> promote(Class<?> c1, Class<?> c2) { |
| if (c1 == c2) |
| return unwrap(c1); |
| Class<?> w1 = wrap(c1); |
| Class<?> w2 = wrap(c2); |
| if (w1 == w2) |
| return unwrap(c1); |
| |
| // not numbers? |
| boolean w1Number = Number.class.isAssignableFrom(w1); |
| boolean w2Number = Number.class.isAssignableFrom(w2); |
| if (!w1Number || !w2Number) { |
| // the only non-numeric promotion we do is string to char, |
| // or from char/string to number |
| if (!w1Number) { |
| if (w2Number && (w1 == Character.class || w1 == String.class)) |
| return (w2 == Byte.class || w2 == Short.class) |
| ? Integer.class : unwrap(c2); |
| if (!w2Number && w1 == Character.class && w2 == String.class) |
| return String.class; |
| if (w2Number) |
| return unwrap(c2); |
| } |
| if (!w2Number) { |
| if (w1Number && (w2 == Character.class || w2 == String.class)) |
| return (w1 == Byte.class || w1 == Short.class) |
| ? Integer.class : unwrap(c1); |
| if (!w1Number && w2 == Character.class && w1 == String.class) |
| return String.class; |
| if (w1Number) |
| return unwrap(c1); |
| } |
| |
| // if neither are numbers, use least-derived of the two. if neither |
| // is assignable from the other but one is a standard type, assume |
| // the other can be converted to that standard type |
| if (!w1Number && !w2Number) { |
| if (w1 == Object.class) |
| return unwrap(c2); |
| if (w2 == Object.class) |
| return unwrap(c1); |
| if (w1.isAssignableFrom(w2)) |
| return unwrap(c1); |
| if (w2.isAssignableFrom(w1)) |
| return unwrap(c2); |
| if (isNonstandardType(w1)) |
| return (isNonstandardType(w2)) ? Object.class : unwrap(c2); |
| if (isNonstandardType(w2)) |
| return (isNonstandardType(w1)) ? Object.class : unwrap(c1); |
| } |
| return Object.class; |
| } |
| |
| if (w1 == BigDecimal.class || w2 == BigDecimal.class) |
| return BigDecimal.class; |
| if (w1 == BigInteger.class) { |
| if (w2 == Float.class || w2 == Double.class) |
| return BigDecimal.class; |
| return BigInteger.class; |
| } |
| if (w2 == BigInteger.class) { |
| if (w1 == Float.class || w1 == Double.class) |
| return BigDecimal.class; |
| return BigInteger.class; |
| } |
| if (w1 == Double.class || w2 == Double.class) |
| return double.class; |
| if (w1 == Float.class || w2 == Float.class) |
| return float.class; |
| if (w1 == Long.class || w2 == Long.class) |
| return long.class; |
| return int.class; |
| } |
| |
| /** |
| * Return whether the given type is not a standard persistent type. |
| */ |
| private static boolean isNonstandardType(Class<?> c) { |
| switch (JavaTypes.getTypeCode(c)) |
| { |
| case JavaTypes.ARRAY: |
| case JavaTypes.COLLECTION: |
| case JavaTypes.MAP: |
| case JavaTypes.PC: |
| case JavaTypes.PC_UNTYPED: |
| case JavaTypes.OID: |
| case JavaTypes.OBJECT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Return whether an instance of the first class can be converted to |
| * an instance of the second. |
| */ |
| public static boolean canConvert(Class<?> c1, Class<?> c2, boolean strict) { |
| if (c1 == c2) |
| return true; |
| c1 = wrap(c1); |
| c2 = wrap(c2); |
| if (c2.isAssignableFrom(c1)) |
| return true; |
| |
| boolean c1Number = Number.class.isAssignableFrom(c1); |
| boolean c2Number = Number.class.isAssignableFrom(c2); |
| if (c1Number && c2Number) |
| return true; |
| if ((c1Number && (c2 == Character.class |
| || (!strict && c2 == String.class))) |
| || (c2Number && (c1 == Character.class |
| || (!strict && c1 == String.class)))) |
| return true; |
| if (c1 == String.class && c2 == Character.class) |
| return true; |
| if (c2 == String.class) |
| return !strict; |
| if (c1 == String.class && isTemporalType(c2)) |
| return true; |
| if ((c1 == java.util.Date.class ||c1 == java.sql.Time.class) && c2 == java.sql.Timestamp.class) |
| return false; |
| if ((c1 == java.util.Date.class ||c1 == java.sql.Timestamp.class) && c2 == java.sql.Time.class) |
| return false; |
| if (isTemporalType(c1) && isTemporalType(c2)) |
| return true; |
| return false; |
| } |
| |
| /** |
| * Convert the given value to match the given (presumably a setter) method argument type. |
| * |
| * @param o given value |
| * @param method a presumably setter method |
| * |
| * @return the same value if the method does not have one and only one input argument. |
| */ |
| public static Object convertToMatchMethodArgument(Object o, Method method) { |
| if (method == null || method.getParameterTypes().length != 1) { |
| return o; |
| } |
| return convert(o, method.getParameterTypes()[0], true); |
| } |
| |
| public static Object convert(Object o, Class<?> type) { |
| return convert(o, type, false); |
| } |
| |
| /** |
| * Convert the given value to the given type. |
| * @param o the given value |
| * @param type the target type |
| */ |
| public static Object convert(Object o, Class<?> type, boolean strictNumericConversion) { |
| if (o == null) |
| return null; |
| if (o.getClass() == type) |
| return o; |
| |
| type = wrap(type); |
| if (type.isAssignableFrom(o.getClass())) |
| return o; |
| |
| // the non-numeric conversions we do are to string, or from |
| // string/char to number, or calendar/date |
| // String to Boolean |
| // String to Integer |
| boolean num = o instanceof Number; |
| if (!num) { |
| if (type == String.class) { |
| return o.toString(); |
| } |
| else if (type == Boolean.class && o instanceof String) { |
| return Boolean.valueOf(o.toString()); |
| } |
| else if (type == Integer.class && o instanceof String) { |
| try { |
| return new Integer(o.toString()); |
| } |
| catch (NumberFormatException e) { |
| throw new ClassCastException(_loc.get("cant-convert", o, o.getClass(), type).getMessage()); |
| } |
| } |
| else if (type == Character.class) { |
| String str = o.toString(); |
| if (str != null && str.length() == 1) { |
| return str.charAt(0); |
| } |
| } |
| else if (Calendar.class.isAssignableFrom(type) && o instanceof Date) { |
| Calendar cal = Calendar.getInstance(); |
| cal.setTime((Date) o); |
| return cal; |
| } |
| else if (Date.class.isAssignableFrom(type) && o instanceof Calendar) { |
| return ((Calendar) o).getTime(); |
| } |
| else if (Number.class.isAssignableFrom(type)) { |
| Integer i = null; |
| if (o instanceof Character) { |
| i = Integer.valueOf((Character) o); |
| } |
| else if (o instanceof String && ((String) o).length() == 1) { |
| i = Integer.valueOf(((String) o)); |
| } |
| |
| if (i != null) { |
| if (type == Integer.class) { |
| return i; |
| } |
| num = true; |
| } |
| } else if (Temporal.class.isAssignableFrom(type)) { |
| // handling of Java8 time API. |
| if (LocalDate.class.equals(type)) { |
| if (o instanceof java.sql.Date) { |
| return ((java.sql.Date) o).toLocalDate(); |
| } else if (o instanceof java.util.Date) { |
| return new java.sql.Date(((java.util.Date)o).getTime()).toLocalDate(); |
| } else if (o instanceof CharSequence) { |
| return LocalDate.parse((CharSequence) o); |
| } |
| } else if (LocalDateTime.class.equals(type)) { |
| if (o instanceof java.sql.Timestamp) { |
| return ((java.sql.Timestamp) o).toLocalDateTime(); |
| } else if (o instanceof java.util.Date) { |
| return ((java.util.Date)o).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); |
| } else if (o instanceof CharSequence) { |
| return LocalDateTime.parse((CharSequence) o); |
| } |
| } else if (LocalTime.class.equals(type)) { |
| if (o instanceof java.sql.Time) { |
| return ((java.sql.Time) o).toLocalTime(); |
| } else if (o instanceof java.util.Date) { |
| return ((java.util.Date)o).toInstant().atZone(ZoneId.systemDefault()).toLocalTime(); |
| } else if (o instanceof CharSequence) { |
| return LocalTime.parse((CharSequence) o); |
| } |
| } else if (OffsetTime.class.equals(type)) { |
| if (o instanceof java.sql.Time) { |
| return ((java.sql.Time) o).toLocalTime().atOffset(OffsetDateTime.now().getOffset()); |
| } else if (o instanceof java.util.Date) { |
| return ((java.util.Date)o).toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime().toOffsetTime(); |
| } else if (o instanceof CharSequence) { |
| return OffsetTime.parse((CharSequence) o); |
| } |
| } else if (OffsetDateTime.class.equals(type)) { |
| if (o instanceof java.sql.Timestamp) { |
| return ((java.sql.Timestamp) o).toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime(); |
| } else if (o instanceof java.util.Date) { |
| return ((java.util.Date)o).toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime(); |
| } else if (o instanceof CharSequence) { |
| return LocalTime.parse((CharSequence) o); |
| } |
| } |
| |
| } else if (o instanceof String && isJDBCTemporalSyntax(o.toString())) { |
| try { |
| Object temporal = parseJDBCTemporalSyntax(o.toString()); |
| if (temporal != null && type.isAssignableFrom(temporal.getClass())) |
| return temporal; |
| } catch (IllegalArgumentException e) { |
| |
| } |
| } else if (o instanceof String && type.isEnum()) { |
| return Enum.valueOf((Class<Enum>)type, o.toString()); |
| } |
| } |
| if (!num) { |
| throw new ClassCastException(_loc.get("cant-convert", o, o.getClass(), type).getMessage()); |
| } |
| |
| if (type == Integer.class && allowNumericConversion(o.getClass(), type, strictNumericConversion)) { |
| return ((Number) o).intValue(); |
| } else if (type == Float.class && allowNumericConversion(o.getClass(), type, strictNumericConversion)) { |
| return ((Number) o).floatValue(); |
| } else if (type == Double.class) { |
| return ((Number) o).doubleValue(); |
| } else if (type == Long.class && allowNumericConversion(o.getClass(), type, strictNumericConversion)) { |
| return ((Number) o).longValue(); |
| } else if (type == BigDecimal.class) { |
| // the BigDecimal constructor doesn't handle the |
| // "NaN" string version of Double.NaN and Float.NaN, nor |
| // does it handle infinity; we need to instead use the Double |
| // and Float versions, despite wanting to cast it to BigDecimal |
| double dval = ((Number) o).doubleValue(); |
| if (Double.isNaN(dval) || Double.isInfinite(dval)) { |
| return dval; |
| } |
| float fval = ((Number) o).floatValue(); |
| if (Float.isNaN(fval) || Float.isInfinite(fval)) { |
| return fval; |
| } |
| |
| return new BigDecimal(o.toString()); |
| } else if (type == BigInteger.class) { |
| return new BigInteger(o.toString()); |
| } else if (type == Short.class && allowNumericConversion(o.getClass(), type, strictNumericConversion)) { |
| return ((Number) o).shortValue(); |
| } else if (type == Byte.class && allowNumericConversion(o.getClass(), type, strictNumericConversion)) { |
| return ((Number) o).byteValue(); |
| } else if (type == Character.class) { |
| return (char) ((Number) o).intValue(); |
| } else if (!strictNumericConversion) { |
| return ((Number) o).intValue(); |
| } else { |
| throw new ClassCastException(_loc.get("cant-convert", o, o.getClass(), type).getMessage()); |
| } |
| } |
| |
| private static boolean allowNumericConversion(Class<?> actual, Class<?> target, boolean strict) { |
| if (!strict || actual == target) |
| return true; |
| if (actual == Byte.class) return false; |
| if (actual == Double.class) return target == Float.class; |
| if (actual == Float.class) return target == Double.class; |
| if (actual == Integer.class) return target == Long.class || target == Short.class; |
| if (actual == Long.class) return target == Integer.class || target == Short.class; |
| if (actual == Short.class) return target == Long.class || target == Integer.class; |
| return false; |
| } |
| |
| /** |
| * Add the given values. |
| */ |
| public static Object add(Object o1, Class<?> c1, Object o2, Class<?> c2) { |
| return op(o1, c1, o2, c2, OP_ADD); |
| } |
| |
| /** |
| * Subtract the given values. |
| */ |
| public static Object subtract(Object o1, Class<?> c1, Object o2, Class<?> c2) { |
| return op(o1, c1, o2, c2, OP_SUBTRACT); |
| } |
| |
| /** |
| * Multiply the given values. |
| */ |
| public static Object multiply(Object o1, Class<?> c1, Object o2, Class<?> c2) { |
| return op(o1, c1, o2, c2, OP_MULTIPLY); |
| } |
| |
| /** |
| * Divide the given values. |
| */ |
| public static Object divide(Object o1, Class<?> c1, Object o2, Class<?> c2) { |
| return op(o1, c1, o2, c2, OP_DIVIDE); |
| } |
| |
| /** |
| * Mod the given values. |
| */ |
| public static Object mod(Object o1, Class<?> c1, Object o2, Class<?> c2) { |
| return op(o1, c1, o2, c2, OP_MOD); |
| } |
| |
| /** |
| * Perform the given operation on two numbers. |
| */ |
| private static Object op(Object o1, Class<?> c1, Object o2, Class<?> c2, int op) { |
| Class<?> promote = promote(c1, c2); |
| if (promote == int.class) { |
| int n1 = (o1 == null) ? 0 : ((Number) o1).intValue(); |
| int n2 = (o2 == null) ? 0 : ((Number) o2).intValue(); |
| return op(n1, n2, op); |
| } |
| if (promote == float.class) { |
| float n1 = (o1 == null) ? 0F : ((Number) o1).floatValue(); |
| float n2 = (o2 == null) ? 0F : ((Number) o2).floatValue(); |
| return op(n1, n2, op); |
| } |
| if (promote == double.class) { |
| double n1 = (o1 == null) ? 0D : ((Number) o1).doubleValue(); |
| double n2 = (o2 == null) ? 0D : ((Number) o2).doubleValue(); |
| return op(n1, n2, op); |
| } |
| if (promote == long.class) { |
| long n1 = (o1 == null) ? 0L : ((Number) o1).longValue(); |
| long n2 = (o2 == null) ? 0L : ((Number) o2).longValue(); |
| return op(n1, n2, op); |
| } |
| if (promote == BigDecimal.class) { |
| BigDecimal n1 = (o1 == null) ? ZERO_BIGDECIMAL |
| : (BigDecimal) convert(o1, promote); |
| BigDecimal n2 = (o2 == null) ? ZERO_BIGDECIMAL |
| : (BigDecimal) convert(o2, promote); |
| return op(n1, n2, op); |
| } |
| if (promote == BigInteger.class) { |
| BigInteger n1 = (o1 == null) ? ZERO_BIGINTEGER |
| : (BigInteger) convert(o1, promote); |
| BigInteger n2 = (o2 == null) ? ZERO_BIGINTEGER |
| : (BigInteger) convert(o2, promote); |
| return op(n1, n2, op); |
| } |
| // default to int |
| int n1 = (o1 == null) ? 0 : ((Number) o1).intValue(); |
| int n2 = (o2 == null) ? 0 : ((Number) o2).intValue(); |
| return op(n1, n2, op); |
| } |
| |
| /** |
| * Return the result of a mathematical operation. |
| */ |
| private static Object op(int n1, int n2, int op) { |
| int tot; |
| switch (op) { |
| case OP_ADD: |
| tot = n1 + n2; |
| break; |
| case OP_SUBTRACT: |
| tot = n1 - n2; |
| break; |
| case OP_MULTIPLY: |
| tot = n1 * n2; |
| break; |
| case OP_DIVIDE: |
| tot = n1 / n2; |
| break; |
| case OP_MOD: |
| tot = n1 % n2; |
| break; |
| default: |
| throw new InternalException(); |
| } |
| return tot; |
| } |
| |
| /** |
| * Return the result of a mathematical operation. |
| */ |
| private static Object op(float n1, float n2, int op) { |
| float tot; |
| switch (op) { |
| case OP_ADD: |
| tot = n1 + n2; |
| break; |
| case OP_SUBTRACT: |
| tot = n1 - n2; |
| break; |
| case OP_MULTIPLY: |
| tot = n1 * n2; |
| break; |
| case OP_DIVIDE: |
| tot = n1 / n2; |
| break; |
| case OP_MOD: |
| tot = n1 % n2; |
| break; |
| default: |
| throw new InternalException(); |
| } |
| return tot; |
| } |
| |
| /** |
| * Return the result of a mathematical operation. |
| */ |
| private static Object op(double n1, double n2, int op) { |
| double tot; |
| switch (op) { |
| case OP_ADD: |
| tot = n1 + n2; |
| break; |
| case OP_SUBTRACT: |
| tot = n1 - n2; |
| break; |
| case OP_MULTIPLY: |
| tot = n1 * n2; |
| break; |
| case OP_DIVIDE: |
| tot = n1 / n2; |
| break; |
| case OP_MOD: |
| tot = n1 % n2; |
| break; |
| default: |
| throw new InternalException(); |
| } |
| return tot; |
| } |
| |
| /** |
| * Return the result of a mathematical operation. |
| */ |
| private static Object op(long n1, long n2, int op) { |
| long tot; |
| switch (op) { |
| case OP_ADD: |
| tot = n1 + n2; |
| break; |
| case OP_SUBTRACT: |
| tot = n1 - n2; |
| break; |
| case OP_MULTIPLY: |
| tot = n1 * n2; |
| break; |
| case OP_DIVIDE: |
| tot = n1 / n2; |
| break; |
| case OP_MOD: |
| tot = n1 % n2; |
| break; |
| default: |
| throw new InternalException(); |
| } |
| return tot; |
| } |
| |
| /** |
| * Return the result of a mathematical operation. |
| */ |
| private static Object op(BigDecimal n1, BigDecimal n2, int op) { |
| switch (op) { |
| case OP_ADD: |
| return n1.add(n2); |
| case OP_SUBTRACT: |
| return n1.subtract(n2); |
| case OP_MULTIPLY: |
| return n1.multiply(n2); |
| case OP_DIVIDE: |
| int scale = Math.max(n1.scale(), n2.scale()); |
| return n1.divide(n2, scale, BigDecimal.ROUND_HALF_UP); |
| case OP_MOD: |
| throw new UserException(_loc.get("mod-bigdecimal")); |
| default: |
| throw new InternalException(); |
| } |
| } |
| |
| /** |
| * Return the result of a mathematical operation. |
| */ |
| private static Object op(BigInteger n1, BigInteger n2, int op) { |
| switch (op) { |
| case OP_ADD: |
| return n1.add(n2); |
| case OP_SUBTRACT: |
| return n1.subtract(n2); |
| case OP_MULTIPLY: |
| return n1.multiply(n2); |
| case OP_DIVIDE: |
| return n1.divide(n2); |
| default: |
| throw new InternalException(); |
| } |
| } |
| |
| /** |
| * Parses the given declarations into a list of type, name, type, name... |
| * Returns null if no declarations. Assumes declaration is not an empty |
| * string and is already trimmed (valid assumptions given the checks made |
| * in our setters). |
| * |
| * @param decType the type of declaration being parsed, for use in |
| * error messages |
| */ |
| public static List<String> parseDeclaration(String dec, char split, String decType) { |
| if (dec == null) |
| return null; |
| |
| // watch for common mixups between commas and semis |
| char bad = (char) 0; |
| if (split == ',') |
| bad = ';'; |
| else if (split == ';') |
| bad = ','; |
| |
| char sentinal = ' '; |
| char cur; |
| int start = 0; |
| boolean skipSpace = false; |
| List<String> results = new ArrayList<>(6); |
| for (int i = 0; i < dec.length(); i++) { |
| cur = dec.charAt(i); |
| if (cur == bad) |
| throw new UserException(_loc.get("bad-dec", dec, decType)); |
| if (cur == ' ' && skipSpace) { |
| start++; |
| continue; |
| } |
| |
| skipSpace = false; |
| if (cur != sentinal) |
| continue; |
| |
| // if looking for spaces, look for split char, or vice versa |
| sentinal = (sentinal == ' ') ? split : ' '; |
| results.add(dec.substring(start, i).trim()); |
| start = i + 1; |
| skipSpace = true; |
| } |
| |
| // add last token, if any |
| if (start < dec.length()) |
| results.add(dec.substring(start)); |
| |
| // if not an even number of elements, something is wrong |
| if (results.isEmpty() || results.size() % 2 != 0) |
| throw new UserException(_loc.get("bad-dec", dec, decType)); |
| |
| return results; |
| } |
| |
| /** |
| * Split the given expression list into distinct expressions. Assumes the |
| * given string is not null or of zero length and is already trimmed |
| * (valid assumptions given the checks in our setters and before |
| * this method call). |
| */ |
| public static List<String> splitExpressions(String str, char split, int expected) { |
| if (str == null) |
| return null; |
| |
| List<String> exps = null; |
| int parenDepth = 0; |
| int begin = 0, pos = 0; |
| boolean escape = false; |
| boolean string = false; |
| boolean nonspace = false; |
| char quote = 0; |
| for (char c; pos < str.length(); pos++) { |
| c = str.charAt(pos); |
| if (c == '\\') { |
| escape = !escape; |
| continue; |
| } |
| if (escape) { |
| escape = false; |
| continue; |
| } |
| |
| switch (c) { |
| case '\'': |
| case '"': |
| if (string && quote == c) |
| string = false; |
| else if (!string) { |
| quote = c; |
| string = true; |
| } |
| nonspace = true; |
| break; |
| case '(': |
| if (!string) |
| parenDepth++; |
| nonspace = true; |
| break; |
| case ')': |
| if (!string) |
| parenDepth--; |
| nonspace = true; |
| break; |
| case ' ': |
| case '\t': |
| case '\n': |
| case '\r': |
| if (c == split && !string && parenDepth == 0 && nonspace) { |
| if (exps == null) |
| exps = new ArrayList<>(expected); |
| exps.add(str.substring(begin, pos).trim()); |
| begin = pos + 1; |
| nonspace = false; |
| } |
| break; |
| default: |
| if (c == split && !string && parenDepth == 0) { |
| if (exps == null) |
| exps = new ArrayList<>(expected); |
| exps.add(str.substring(begin, pos).trim()); |
| begin = pos + 1; |
| } |
| nonspace = true; |
| } |
| escape = false; |
| } |
| |
| if (exps == null) { |
| exps = Collections.singletonList(str); |
| return exps; |
| } |
| |
| // add last expression and return array |
| String last = str.substring(begin).trim(); |
| if (last.length() > 0) |
| exps.add(last); |
| return exps; |
| } |
| |
| /** |
| * Add the given access path metadatas to the full path list, making sure |
| * to maintain only base metadatas in the list. The given list may be null. |
| */ |
| public static List<ClassMetaData> addAccessPathMetaDatas(List<ClassMetaData> metas, ClassMetaData[] path) { |
| if (path == null || path.length == 0) |
| return metas; |
| |
| // create set of base class metadatas in access path |
| if (metas == null) |
| metas = new ArrayList<>(); |
| int last = metas.size(); |
| |
| // for every element in the path of this executor, compare it |
| // to already-gathered elements to see if it should replace |
| // a subclass in the list or should be added as a new base; |
| // at least it's n^2 of a small n... |
| ClassMetaData meta; |
| boolean add; |
| for (int i = 0; i < path.length; i++) { |
| add = true; |
| for (int j = 0; add && j < last; j++) { |
| meta = metas.get(j); |
| |
| if (meta.getDescribedType().isAssignableFrom(path[i].getDescribedType())) { |
| // list already contains base class |
| add = false; |
| } else if (path[i].getDescribedType().isAssignableFrom(meta.getDescribedType())) { |
| // this element replaces its subclass |
| add = false; |
| metas.set(j, path[i]); |
| } |
| } |
| |
| // if no base class of current path element already in |
| // list and path element didn't replace a subclass in the |
| // list, then add it now as a new base |
| if (add) |
| metas.add(path[i]); |
| } |
| return metas; |
| } |
| |
| /** |
| * Convert the user-given hint value to an aggregate listener. |
| * The hint can be an aggregate listener instance or class name. |
| */ |
| public static AggregateListener hintToAggregateListener(Object hint, ClassLoader loader) { |
| if (hint == null) |
| return null; |
| if (hint instanceof AggregateListener) |
| return (AggregateListener) hint; |
| |
| Exception cause = null; |
| if (hint instanceof String) { |
| try { |
| return (AggregateListener) AccessController.doPrivileged( |
| J2DoPrivHelper.newInstanceAction(Class.forName((String) hint, true, loader))); |
| } catch (Exception e) { |
| if (e instanceof PrivilegedActionException) |
| e = ((PrivilegedActionException) e).getException(); |
| cause = e; |
| } |
| } |
| throw new UserException(_loc.get("bad-agg-listener-hint", hint, |
| hint.getClass())).setCause(cause); |
| } |
| |
| /** |
| * Convert the user-given hint value to an array of aggregate listeners. |
| * The hint can be an aggregate listener, aggregate listener array, |
| * collection, or comma-separated class names. |
| */ |
| public static AggregateListener[] hintToAggregateListeners(Object hint, ClassLoader loader) { |
| if (hint == null) |
| return null; |
| if (hint instanceof AggregateListener[]) |
| return (AggregateListener[]) hint; |
| if (hint instanceof AggregateListener) |
| return new AggregateListener[]{ (AggregateListener) hint }; |
| if (hint instanceof Collection) { |
| Collection<AggregateListener> c = (Collection<AggregateListener>) hint; |
| return c.toArray(new AggregateListener[c.size()]); |
| } |
| |
| Exception cause = null; |
| if (hint instanceof String) { |
| String[] clss = StringUtil.split((String) hint, ",", 0); |
| AggregateListener[] aggs = new AggregateListener[clss.length]; |
| try { |
| for (int i = 0; i < clss.length; i++) |
| aggs[i] = (AggregateListener) AccessController.doPrivileged( |
| J2DoPrivHelper.newInstanceAction( |
| Class.forName(clss[i], true, loader))); |
| return aggs; |
| } catch (Exception e) { |
| if (e instanceof PrivilegedActionException) |
| e = ((PrivilegedActionException) e).getException(); |
| cause = e; |
| } |
| } |
| throw new UserException(_loc.get("bad-agg-listener-hint", hint, |
| hint.getClass())).setCause(cause); |
| } |
| |
| /** |
| * Convert the user-given hint value to a filter listener. |
| * The hint can be a filter listener instance or class name. |
| */ |
| public static FilterListener hintToFilterListener(Object hint, ClassLoader loader) { |
| if (hint == null) |
| return null; |
| if (hint instanceof FilterListener) |
| return (FilterListener) hint; |
| |
| Exception cause = null; |
| if (hint instanceof String) { |
| try { |
| return (FilterListener) AccessController.doPrivileged( |
| J2DoPrivHelper.newInstanceAction( |
| Class.forName((String) hint, true, loader))); |
| } catch (Exception e) { |
| if (e instanceof PrivilegedActionException) |
| e = ((PrivilegedActionException) e).getException(); |
| cause = e; |
| } |
| } |
| throw new UserException(_loc.get("bad-filter-listener-hint", hint, |
| hint.getClass())).setCause(cause); |
| } |
| |
| /** |
| * Convert the user-given hint value to an array of filter listeners. |
| * The hint can be a filter listener, filter listener array, |
| * collection, or comma-separated class names. |
| */ |
| public static FilterListener[] hintToFilterListeners(Object hint, ClassLoader loader) { |
| if (hint == null) |
| return null; |
| if (hint instanceof FilterListener[]) |
| return (FilterListener[]) hint; |
| if (hint instanceof FilterListener) |
| return new FilterListener[]{ (FilterListener) hint }; |
| if (hint instanceof Collection) { |
| Collection<FilterListener> c = (Collection<FilterListener>) hint; |
| return c.toArray(new FilterListener[c.size()]); |
| } |
| |
| Exception cause = null; |
| if (hint instanceof String) { |
| String[] clss = StringUtil.split((String) hint, ",", 0); |
| FilterListener[] filts = new FilterListener[clss.length]; |
| try { |
| for (int i = 0; i < clss.length; i++) |
| filts[i] = (FilterListener) AccessController.doPrivileged( |
| J2DoPrivHelper.newInstanceAction( |
| Class.forName(clss[i], true, loader))); |
| return filts; |
| } catch (Exception e) { |
| if (e instanceof PrivilegedActionException) |
| e = ((PrivilegedActionException) e).getException(); |
| cause = e; |
| } |
| } |
| throw new UserException(_loc.get("bad-filter-listener-hint", hint, |
| hint.getClass())).setCause(cause); |
| } |
| |
| /** |
| * Return the value of the property named by the hint key. |
| */ |
| public static Object hintToGetter(Object target, String hintKey) { |
| if (target == null || hintKey == null) |
| return null; |
| |
| Method getter = Reflection.findGetter(target.getClass(), hintKey, true); |
| return Reflection.get(target, getter); |
| } |
| |
| /** |
| * Set the value of the property named by the hint key. |
| */ |
| public static void hintToSetter(Object target, String hintKey, Object value) { |
| if (target == null || hintKey == null) |
| return; |
| |
| Method setter = Reflection.findSetter(target.getClass(), hintKey, true); |
| if (value instanceof String) { |
| if ("null".equals(value)) |
| value = null; |
| else { |
| try { |
| value = StringUtil.parse((String) value, setter.getParameterTypes()[0]); |
| } catch (Exception e) { |
| throw new UserException(_loc.get("bad-setter-hint-arg", |
| hintKey, value, setter.getParameterTypes()[0])). |
| setCause(e); |
| } |
| } |
| } |
| Reflection.set(target, setter, value); |
| } |
| |
| /** |
| * Parses the given string assuming it is a JDBC key expression. Extracts the |
| * data portion and based on the key, calls static java.sql.Date/Time/Timestamp.valueOf(String) |
| * method to convert to a java.sql.Date/Time/Timestamp instance. |
| */ |
| public static Object parseJDBCTemporalSyntax(String s) { |
| s = clip(s.trim(), "{", "}", true); |
| if (s.startsWith("ts")) { |
| return java.sql.Timestamp.valueOf(clip(s.substring(2).trim(), "'", "'", false)); |
| } else if (s.startsWith("d")) { |
| return java.sql.Date.valueOf(clip(s.substring(1).trim(), "'", "'", false)); |
| } else if (s.startsWith("t")) { |
| return java.sql.Time.valueOf(clip(s.substring(2).trim(), "'", "'", false)); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Affirms if the given String is enclosed in {}. |
| * |
| */ |
| public static boolean isJDBCTemporalSyntax(String s) { |
| if (s != null) { |
| s = s.trim(); |
| } |
| return s != null && s.startsWith("{") && s.endsWith("}"); |
| } |
| |
| /** |
| * Removes the first and last string if they are the terminal sequence in the given string. |
| * |
| * @param s a string to be examined |
| * @param first the characters in the beginning of the given string |
| * @param last the characters in the end of the given string |
| * @param fail if true throws exception if the given string does not have the given terminal sequences. |
| * @return the string with terminal sequences removed. |
| */ |
| public static String clip(String s, String first, String last, boolean fail) { |
| if (s == null) |
| return s; |
| if (s.startsWith(first) && s.endsWith(last)) { |
| return s.substring(first.length(), s.length()-last.length()).trim(); |
| } |
| if (fail) { |
| throw new IllegalArgumentException(s + " is not valid escape syntax for JDBC"); |
| } |
| return s; |
| } |
| |
| /** |
| * Affirms if the given class is Data, Time or Timestamp. |
| */ |
| public static boolean isTemporalType(Class<?> c) { |
| return c != null |
| && (Date.class.isAssignableFrom(c) |
| || Time.class.isAssignableFrom(c) |
| || Timestamp.class.isAssignableFrom(c) |
| || Calendar.class.isAssignableFrom(c) |
| || LocalDate.class == c // java.time classes are final, so we can compare with == |
| || LocalDateTime.class == c |
| || LocalTime.class == c |
| || OffsetTime.class ==c |
| || OffsetDateTime.class == c); |
| } |
| |
| public static Object getDefaultForNull(Class<?> nType) { |
| if (nType == Long.class) |
| return 0L; |
| if (nType == Integer.class) |
| return 0; |
| if (nType == Double.class) |
| return 0.0; |
| if (nType == Float.class) |
| return 0.0F; |
| if (nType == Short.class) |
| return (short) 0; |
| return null; |
| } |
| |
| } |