/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.jboss.errai.jpa.client.local.backend;
import java.util.Collection;
/**
* Non-instantiable utility methods for comparing two or more values.
*
* @author Jonathan Fuerth <jfuerth@gmail.com>
*/
public class Comparisons {
/**
* Tests two potentially null object references for equality using approximate
* JPQL/SQL null semantics.
* <p>
* Specifically, this method returns true if and only if both arguments are
* non-null and either of the following conditions are met:
* <ol>
* <li>{@code o1 == o2}
* <li>{@code o1.equals(o2)}
* </ol>
*
* @param o1
* One object to compare. Null is permitted.
* @param o2
* The other object to compare. Null is permitted.
* @return true if o1 and o2 are equal (either by reference equality or by
* Object.equals()); false otherwise
*/
// MAINTAINERS BEWARE: Errai JPA generates code that uses this method.
public static boolean nullSafeEquals(Object o1, Object o2) {
return o1 != null && o2 != null && (o1 == o2 || o1.equals(o2));
}
/**
* Tests if one potentially null object reference is greater than another.
*
* @param o1
* One object to compare. Null is permitted.
* @param o2
* The other object to compare. Null is permitted.
* @return true if {@code o1 > o2} (either by primitive comparison or by
* Comparable.compareTo()); false otherwise
*/
// MAINTAINERS BEWARE: Errai JPA generates code that uses this method.
public static boolean nullSafeGreaterThan(Object o1, Object o2) {
if (o1 == null || o2 == null) return false;
if (o1 instanceof Number && o2 instanceof Number) {
return ((Number) o1).doubleValue() > ((Number) o2).doubleValue();
}
if (o1 instanceof Comparable<?>) {
return ((Comparable<Object>) o1).compareTo(o2) > 0;
}
throw new IllegalArgumentException(
"Can't compare an instance of " + o1.getClass() + " to an instance of " + o2.getClass());
}
/**
* Tests if one potentially null object reference is greater than another.
*
* @param o1
* One object to compare. Null is permitted.
* @param o2
* The other object to compare. Null is permitted.
* @return true if {@code o1 > o2} (either by primitive comparison or by
* Comparable.compareTo()); false otherwise
*/
// MAINTAINERS BEWARE: Errai JPA generates code that uses this method.
public static boolean nullSafeGreaterThanOrEqualTo(Object o1, Object o2) {
if (o1 == null || o2 == null) return false;
if (o1 instanceof Number && o2 instanceof Number) {
return ((Number) o1).doubleValue() >= ((Number) o2).doubleValue();
}
if (o1 instanceof Comparable<?>) {
return ((Comparable<Object>) o1).compareTo(o2) >= 0;
}
throw new IllegalArgumentException(
"Can't compare an instance of " + o1.getClass() + " to an instance of " + o2.getClass());
}
/**
* Tests if one potentially null object reference is greater than another.
*
* @param o1
* One object to compare. Null is permitted.
* @param o2
* The other object to compare. Null is permitted.
* @return true if {@code o1 > o2} (either by primitive comparison or by
* Comparable.compareTo()); false otherwise
*/
// MAINTAINERS BEWARE: Errai JPA generates code that uses this method.
public static boolean nullSafeLessThan(Object o1, Object o2) {
if (o1 == null || o2 == null) return false;
if (o1 instanceof Number && o2 instanceof Number) {
return ((Number) o1).doubleValue() < ((Number) o2).doubleValue();
}
if (o1 instanceof Comparable<?>) {
return ((Comparable<Object>) o1).compareTo(o2) < 0;
}
throw new IllegalArgumentException(
"Can't compare an instance of " + o1.getClass() + " to an instance of " + o2.getClass());
}
/**
* Tests if one potentially null object reference is greater than another.
*
* @param o1
* One object to compare. Null is permitted.
* @param o2
* The other object to compare. Null is permitted.
* @return true if {@code o1 > o2} (either by primitive comparison or by
* Comparable.compareTo()); false otherwise
*/
// MAINTAINERS BEWARE: Errai JPA generates code that uses this method.
public static boolean nullSafeLessThanOrEqualTo(Object o1, Object o2) {
if (o1 == null || o2 == null) return false;
if (o1 instanceof Number && o2 instanceof Number) {
return ((Number) o1).doubleValue() <= ((Number) o2).doubleValue();
}
if (o1 instanceof Comparable<?>) {
return ((Comparable<Object>) o1).compareTo(o2) <= 0;
}
throw new IllegalArgumentException(
"Can't compare an instance of " + o1.getClass() + " to an instance of " + o2.getClass());
}
/**
* Compares one potentially null Comparable to another.
*
* @param c1
* One object to compare. Null is permitted.
* @param c2
* The other object to compare. Null is permitted.
* @return 0 if c1 and c2 are both null; -1 if c1 is null and c2 is not; 1 if
* c1 is not null and c2 is. Otherwise returns c1.compareTo(c2).
*/
// MAINTAINERS BEWARE: Errai JPA generates code that uses this method.
public static int nullSafeCompare(Comparable c1, Comparable c2) {
if (c1 == null && c2 == null) return 0;
if (c1 == null && c2 != null) return -1;
if (c1 != null && c2 == null) return 1;
return c1.compareTo(c2);
}
/**
* Tests if the first argument is equal to any of the remaining arguments.
* Equality is tested using {@link #nullSafeEquals(Object, Object)}.
* <p>
* <b>Special Case</b><br>
* If the collection has only one item in it, and that item is assignable to
* Collection, then that collection will be searched rather than being treated
* as a single scalar value. This allows correct behaviour for a JPQL query
* {@code SELECT x FROM MyClass x WHERE x.prop IN :param} and {@code param}
* resolves to a collection value at runtime.
*
* @param thingToCompare
* The item to compare against the remaining arguments.
* @param collection
* One or more items to test for equality with {@code thingToCompare}
* .
* @return True if there is an item in {@code collection} that compares equal
* with {@code thingToCompare}. False otherwise.
*/
// MAINTAINERS BEWARE: Errai JPA generates code that uses this method.
public static boolean in(Object thingToCompare, Object[] collection) {
// special case: if the collection only has one member and it's a collection, we unwrap it.
// this provides the required JPQL behaviour.
if (collection.length == 1 && collection[0] instanceof Collection) {
for (Object o : (Collection<?>) collection[0]) {
if (nullSafeEquals(thingToCompare, o)) {
return true;
}
}
}
else {
for (Object o : collection) {
if (nullSafeEquals(thingToCompare, o)) {
return true;
}
}
}
return false;
}
/**
* Checks of the given value matches the given JPQL wildcard pattern.
*
* @param value
* The string value to test. May be null.
* @param pattern
* The JPQL pattern to test against. Special characters are "{@code _}
* ", which matches any single character, and "{@code %}", which
* matches 0 or more characters.
*/
// MAINTAINERS BEWARE: Errai JPA generates code that uses this method.
public static Boolean like(String value, String pattern, String escapeChar) {
if (value == null || pattern == null) {
return null;
}
return value.matches(sqlWildcardToRegex(pattern, escapeChar));
}
private static String sqlWildcardToRegex(String pattern, String escapeChar) {
char esc;
if (escapeChar == null) {
esc = 'x'; // (not used in this case)
}
else if (escapeChar.length() != 1) {
throw new IllegalArgumentException("In LIKE x ESCAPE e, e must be a single-character string");
}
else {
esc = escapeChar.charAt(0);
}
StringBuilder sb = new StringBuilder(pattern.length());
for (int i = 0; i < pattern.length(); i++) {
char ch = pattern.charAt(i);
if (ch == esc) {
// advance to next character and don't treat as wildcard
ch = pattern.charAt(++i);
}
else if (ch == '_') {
// wildcard: match any one char
sb.append('.');
continue;
}
else if (ch == '%') {
// wildcard: match 0 or more chars
sb.append(".*");
continue;
}
// append non-jpql-wildcard char (escaping if it's a special regex char)
sb.append(escapeRegexChar(ch));
}
return sb.toString();
}
public static String escapeRegexChar(char ch) {
StringBuilder sb = new StringBuilder(2);
switch (ch) {
case '.':
case '\\':
case '+':
case '*':
case '?':
case '[':
case '^':
case ']':
case '$':
case '(':
case ')':
case '{':
case '}':
case '=':
case '!':
case '<':
case '>':
case '|':
case ':':
case '-':
sb.append('\\');
// FALLTHROUGH
default:
sb.append(ch);
}
return sb.toString();
}
}