/***************************************************************** * 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.cayenne.exp.parser; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.cayenne.Cayenne; import org.apache.cayenne.ObjectId; import org.apache.cayenne.Persistent; import org.apache.cayenne.util.ConversionUtil; /** * Performs argument conversions for a calling binary expression, so that the * expression could eval the arguments of the same type. * * @since 4.0 */ abstract class Evaluator { private static final ConcurrentMap<Class<?>, Evaluator> evaluators; private static final Evaluator NULL_LHS_EVALUATOR; private static final Evaluator DEFAULT_EVALUATOR; private static final Evaluator PERSISTENT_EVALUATOR; private static final Evaluator BIG_DECIMAL_EVALUATOR; private static final Evaluator NUMBER_EVALUATOR; private static final Evaluator COMPARABLE_EVALUATOR; private static final double EPSILON = 0.0000001; /** * A decorator of an evaluator that presumes non-null 'lhs' argument and * allows for null 'rhs'. */ static class NonNullLhsEvaluator extends Evaluator { final Evaluator delegate; NonNullLhsEvaluator(Evaluator delegate) { this.delegate = delegate; } @Override Integer compare(Object lhs, Object rhs) { if (rhs == null) { return null; } return delegate.compare(lhs, rhs); } @Override boolean eq(Object lhs, Object rhs) { return rhs != null && delegate.eq(lhs, rhs); } } static { evaluators = new ConcurrentHashMap<>(); NULL_LHS_EVALUATOR = new Evaluator() { @Override Integer compare(Object lhs, Object rhs) { return null; } @Override boolean eq(Object lhs, Object rhs) { return rhs == null; } }; DEFAULT_EVALUATOR = new NonNullLhsEvaluator(new Evaluator() { @Override boolean eq(Object lhs, Object rhs) { return lhs.equals(rhs); } @Override Integer compare(Object lhs, Object rhs) { return null; } }); PERSISTENT_EVALUATOR = new NonNullLhsEvaluator(new Evaluator() { @Override Integer compare(Object lhs, Object rhs) { return null; } @Override boolean eq(Object lhs, Object rhs) { Persistent lhsPersistent = (Persistent) lhs; if (rhs instanceof Persistent) { return lhsPersistent.getObjectId().equals(((Persistent) rhs).getObjectId()); } if (rhs instanceof ObjectId) { return lhsPersistent.getObjectId().equals(rhs); } if (rhs instanceof Map) { return lhsPersistent.getObjectId().getIdSnapshot().equals(rhs); } if (lhsPersistent.getObjectId().getIdSnapshot().size() != 1) { // the only options left below are for the single key IDs return false; } if (rhs instanceof Number) { // only care about whole numbers if (rhs instanceof Integer) { return Cayenne.longPKForObject(lhsPersistent) == ((Number) rhs).longValue(); } if (rhs instanceof Long) { return Cayenne.longPKForObject(lhsPersistent) == ((Number) rhs).longValue(); } } return Cayenne.pkForObject(lhsPersistent).equals(rhs); } }); BIG_DECIMAL_EVALUATOR = new NonNullLhsEvaluator(new Evaluator() { @Override Integer compare(Object lhs, Object rhs) { return ((BigDecimal) lhs).compareTo(ConversionUtil.toBigDecimal(rhs)); } @Override boolean eq(Object lhs, Object rhs) { // BigDecimals must be compared using compareTo (see CAY-280 and BigDecimal.equals JavaDoc) Integer c = compare(lhs, rhs); return c == 0; } }); NUMBER_EVALUATOR = new NonNullLhsEvaluator(new Evaluator() { @SuppressWarnings({ "unchecked", "rawtypes" }) @Override Integer compare(Object lhs, Object rhs) { return compareNumbers((Number)lhs, rhs); } @Override boolean eq(Object lhs, Object rhs) { return equalNumbers((Number)lhs, rhs); } private final List WHOLE_NUMBER_CLASSES = Arrays.asList(Byte.class, Short.class, Integer.class, AtomicInteger.class, Long.class, AtomicLong.class, BigInteger.class); /** * Returns the widest primitive wrapper class given the two operands, used in preparation for * comparing two boxed numbers of different types, like java.lang.Short and java.lang.Integer. */ Class<?> widestNumberType(Number lhs, Number rhs) { if (lhs.getClass().equals(rhs.getClass())) return lhs.getClass(); int lhsIndex = WHOLE_NUMBER_CLASSES.indexOf(lhs.getClass()); int rhsIndex = WHOLE_NUMBER_CLASSES.indexOf(rhs.getClass()); Class<?> widestClass; if (lhsIndex != -1 && rhsIndex != -1) { widestClass = (Class<?>) WHOLE_NUMBER_CLASSES.get(Math.max(lhsIndex, rhsIndex)); } else { widestClass = Double.class; } return widestClass; } /** * Enables equality tests for two boxed numbers of different types, like java.lang.Short and java.lang.Integer. */ boolean equalNumbers(Number lhs, Object _rhs) { if (!Number.class.isAssignableFrom(_rhs.getClass())) { return lhs.equals(_rhs); } Number rhs = (Number) _rhs; Class<?> widestClass = widestNumberType(lhs, rhs); if (Integer.class.equals(widestClass) || AtomicInteger.class.equals(widestClass)) { return lhs.intValue() == rhs.intValue(); } else if (Long.class.equals(widestClass) || AtomicLong.class.equals(widestClass)) { return lhs.longValue() == rhs.longValue(); } else if (Double.class.equals(widestClass)) { return Math.abs(lhs.doubleValue() - rhs.doubleValue()) < EPSILON; } else if (Short.class.equals(widestClass)) { return lhs.shortValue() == rhs.shortValue(); } else if (BigInteger.class.equals(widestClass)) { return lhs.toString().equals(rhs.toString()); } else { return lhs.equals(rhs); } } /** * Enables comparison of two boxed numbers of different types, like java.lang.Short and java.lang.Integer. */ Integer compareNumbers(Number lhs, Object _rhs) { if (!Number.class.isAssignableFrom(_rhs.getClass())) { return null; } Number rhs = (Number) _rhs; Class widestClass = widestNumberType(lhs, rhs); if (Integer.class.equals(widestClass) || AtomicInteger.class.equals(widestClass)) { return Integer.valueOf(lhs.intValue()).compareTo(rhs.intValue()); } else if (Long.class.equals(widestClass) || AtomicLong.class.equals(widestClass)) { return Long.valueOf(lhs.longValue()).compareTo(rhs.longValue()); } else if (Double.class.equals(widestClass)) { boolean areEqual = Math.abs(lhs.doubleValue() - rhs.doubleValue()) < EPSILON; return areEqual ? 0 : Double.compare(lhs.doubleValue(), rhs.doubleValue()); } else if (Float.class.equals(widestClass)) { boolean areEqual = Math.abs(lhs.floatValue() - rhs.floatValue()) < EPSILON; return areEqual ? 0 : Float.compare(lhs.floatValue(), rhs.floatValue()); } else if (Short.class.equals(widestClass)) { return Short.valueOf(lhs.shortValue()).compareTo(rhs.shortValue()); } else if (Byte.class.equals(widestClass)) { return Byte.valueOf(lhs.byteValue()).compareTo(rhs.byteValue()); } else if (BigInteger.class.equals(widestClass)) { BigInteger left = lhs instanceof BigInteger ? (BigInteger)lhs : new BigInteger(lhs.toString()); BigInteger right = rhs instanceof BigInteger ? (BigInteger)rhs : new BigInteger(rhs.toString()); return left.compareTo(right); } else if (Comparable.class.isAssignableFrom(lhs.getClass())) { return ((Comparable)lhs).compareTo(rhs); } else { return null; } } }); COMPARABLE_EVALUATOR = new NonNullLhsEvaluator(new Evaluator() { @SuppressWarnings({ "unchecked", "rawtypes" }) @Override Integer compare(Object lhs, Object rhs) { return ((Comparable) lhs).compareTo(ConversionUtil.toComparable(rhs)); } @Override boolean eq(Object lhs, Object rhs) { return lhs.equals(rhs); } }); } static Evaluator evaluator(Object lhs) { if (lhs == null) { return NULL_LHS_EVALUATOR; } Class<?> lhsType = lhs.getClass(); Evaluator e = evaluators.get(lhsType); if (e == null) { Evaluator created = compileEvaluator(lhsType); Evaluator existing = evaluators.putIfAbsent(lhsType, created); e = existing != null ? existing : created; } return e; } private static Evaluator compileEvaluator(Class<?> lhsType) { Evaluator ev = findInHierarchy(lhsType); if (ev != null) { return ev; } // check known interfaces if (Persistent.class.isAssignableFrom(lhsType)) { return PERSISTENT_EVALUATOR; } if (BigDecimal.class.isAssignableFrom(lhsType)) { return BIG_DECIMAL_EVALUATOR; } if (Number.class.isAssignableFrom(lhsType)) { return NUMBER_EVALUATOR; } if (Comparable.class.isAssignableFrom(lhsType)) { return COMPARABLE_EVALUATOR; } // nothing we recognize... return default return DEFAULT_EVALUATOR; } private static Evaluator findInHierarchy(Class<?> lhsType) { if (Object.class.equals(lhsType)) { return null; } Evaluator ev = evaluators.get(lhsType); return (ev != null) ? ev : findInHierarchy(lhsType.getSuperclass()); } abstract boolean eq(Object lhs, Object rhs); /** * Returns NULL if comparison is invalid, otherwise returns positive, * negative or zero, with the same meaning as * {@link Comparable#compareTo(Object)}. */ abstract Integer compare(Object lhs, Object rhs); }