blob: b1eb3185f881486f1b06830fe02da45f5586a689 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2016 Cloudsmith Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cloudsmith Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata.expression;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Comparator;
import org.eclipse.equinox.internal.p2.metadata.MetadataActivator;
import org.eclipse.equinox.p2.metadata.Version;
/**
* A comparator that performs coercion if needed before comparison.
* @param <T> The type for the comparator.
*/
public abstract class CoercingComparator<T> {
static class BooleanCoercer extends CoercingComparator<Boolean> {
public int compare(Boolean o1, Boolean o2) {
return o1.booleanValue() == o2.booleanValue() ? 0 : (o1.booleanValue() ? 1 : -1);
}
@Override
Boolean coerce(Object v) {
if (v instanceof Boolean)
return (Boolean) v;
if (v instanceof String) {
String sv = ((String) v).trim();
if (sv.equalsIgnoreCase("true")) //$NON-NLS-1$
return Boolean.TRUE;
if (sv.equalsIgnoreCase("false")) //$NON-NLS-1$
return Boolean.FALSE;
}
throw uncoercable(v);
}
@Override
Class<Boolean> getCoerceClass() {
return Boolean.class;
}
@Override
int getCoercePrio() {
return 7;
}
}
static class ClassCoercer extends CoercingComparator<Class<?>> {
public int compare(Class<?> o1, Class<?> o2) {
return o1.getName().compareTo(o2.getName());
}
@Override
Class<?> coerce(Object v) {
if (v instanceof Class<?>)
return (Class<?>) v;
if (v instanceof String) {
try {
return MetadataActivator.getContext().getBundle().loadClass(((String) v).trim());
} catch (Exception e) {
//
}
}
throw uncoercable(v);
}
@SuppressWarnings("unchecked")
@Override
Class<Class<?>> getCoerceClass() {
Class<?> cls = Class.class;
return (Class<Class<?>>) cls;
}
@Override
int getCoercePrio() {
return 11;
}
}
static class FromStringCoercer<T extends Comparable<Object>> extends CoercingComparator<T> {
private final Class<T> coerceClass;
private final Constructor<T> constructor;
public FromStringCoercer(Class<T> coerceClass, Constructor<T> constructor) {
this.coerceClass = coerceClass;
this.constructor = constructor;
}
@Override
T coerce(Object v) {
if (v instanceof String) {
try {
return constructor.newInstance(new Object[] {((String) v).trim()});
} catch (Exception e) {
//
}
}
throw uncoercable(v);
}
@Override
int compare(T o1, T o2) {
return o1.compareTo(o2);
}
@Override
Class<T> getCoerceClass() {
return coerceClass;
}
@Override
int getCoercePrio() {
return 0;
}
}
static class IntegerCoercer extends CoercingComparator<Integer> {
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
@Override
Integer coerce(Object v) {
if (v instanceof Integer)
return (Integer) v;
if (v instanceof Number)
return Integer.valueOf(((Number) v).intValue());
if (v instanceof String) {
try {
return Integer.valueOf(((String) v).trim());
} catch (NumberFormatException e) {
//
}
}
throw uncoercable(v);
}
@Override
Class<Integer> getCoerceClass() {
return Integer.class;
}
@Override
int getCoercePrio() {
return 6;
}
}
static class LongCoercer extends CoercingComparator<Long> {
public int compare(Long o1, Long o2) {
return o1.compareTo(o2);
}
@Override
Long coerce(Object v) {
if (v instanceof Long)
return (Long) v;
if (v instanceof Number)
return new Long(((Number) v).longValue());
if (v instanceof String) {
try {
return Long.valueOf(((String) v).trim());
} catch (NumberFormatException e) {
//
}
}
throw uncoercable(v);
}
@Override
Class<Long> getCoerceClass() {
return Long.class;
}
@Override
int getCoercePrio() {
return 5;
}
}
static class StringCoercer extends CoercingComparator<String> {
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
@Override
String coerce(Object v) {
if (v instanceof Class<?>)
return ((Class<?>) v).getName();
return v.toString();
}
@Override
Class<String> getCoerceClass() {
return String.class;
}
@Override
int getCoercePrio() {
return 10;
}
}
static class VersionCoercer extends CoercingComparator<Version> {
public int compare(Version o1, Version o2) {
return o1.compareTo(o2);
}
boolean canCoerceTo(Class<?> cls) {
return Version.class.isAssignableFrom(cls);
}
@Override
Version coerce(Object v) {
if (v instanceof Version)
return (Version) v;
if (v instanceof String)
return Version.create((String) v);
if (v instanceof String) {
try {
return Version.create((String) v);
} catch (NumberFormatException e) {
//
}
}
throw uncoercable(v);
}
@Override
Class<Version> getCoerceClass() {
return Version.class;
}
@Override
int getCoercePrio() {
return 1;
}
}
private static class SetAccessibleAction implements PrivilegedAction<Object> {
private final AccessibleObject accessible;
SetAccessibleAction(AccessibleObject accessible) {
this.accessible = accessible;
}
public Object run() {
accessible.setAccessible(true);
return null;
}
}
private static CoercingComparator<?>[] coercers = {new ClassCoercer(), new BooleanCoercer(), new LongCoercer(), new IntegerCoercer(), new VersionCoercer(), new StringCoercer()};
private static final Class<?>[] constructorType = new Class<?>[] {String.class};
/**
* Finds the comparator for <code>a</code> and <code>b</code> and delegates the coercion/comparison to the comparator
* according to priority.
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return The result of the comparison
* @throws IllegalArgumentException if no comparator was found or if coercion was impossible
* @see Comparator#compare(Object, Object)
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <TA extends Object, TB extends Object> int coerceAndCompare(TA o1, TB o2) throws IllegalArgumentException {
if (o1 == null || o2 == null)
throw new IllegalArgumentException("Cannot compare null to anything"); //$NON-NLS-1$
if (o1 instanceof Comparable && o1.getClass().isAssignableFrom(o2.getClass()))
return ((Comparable) o1).compareTo(o2);
if (o2 instanceof Comparable && o2.getClass().isAssignableFrom(o1.getClass()))
return -((Comparable) o2).compareTo(o1);
CoercingComparator<TA> ca = getComparator(o1, o2);
CoercingComparator<TB> cb = getComparator(o2, o1);
return ca.getCoercePrio() <= cb.getCoercePrio() ? ca.compare(o1, ca.coerce(o2)) : cb.compare(cb.coerce(o1), o2);
}
/**
* Finds the comparator for <code>a</code> and <code>b</code> and delegates the coercion/equal to the comparator
* according to priority.
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return The result of the equality test
* @throws IllegalArgumentException if no comparator was found or if coercion was impossible
* @see Object#equals(Object)
*/
public static <TA extends Object, TB extends Object> boolean coerceAndEquals(TA o1, TB o2) throws IllegalArgumentException {
if (o1 == o2)
return true;
if (o1 == null || o2 == null)
return false;
if (o1.getClass() != o2.getClass()) {
if (o1.getClass().isAssignableFrom(o2.getClass()))
return o1.equals(o2);
if (o2.getClass().isAssignableFrom(o1.getClass()))
return o2.equals(o1);
try {
CoercingComparator<TA> ca = getComparator(o1, o2);
CoercingComparator<TB> cb = getComparator(o2, o1);
return ca.getCoercePrio() <= cb.getCoercePrio() ? o1.equals(ca.coerce(o2)) : o2.equals(cb.coerce(o1));
} catch (IllegalArgumentException e) {
//
}
}
return o1.equals(o2);
}
/**
* Obtains the coercing comparator for the given <code>value</code>.
* @param value The value
* @return The coercing comparator
*/
@SuppressWarnings("unchecked")
public static <V extends Object> CoercingComparator<V> getComparator(V value, Object v2) {
Class<V> vClass = (Class<V>) value.getClass();
CoercingComparator<?>[] carr = coercers;
int idx = carr.length;
while (--idx >= 0) {
CoercingComparator<?> c = carr[idx];
if (c.canCoerceTo(vClass)) {
CoercingComparator<V> cv = (CoercingComparator<V>) c;
return cv;
}
}
if (value instanceof Comparable<?> && v2 instanceof String) {
Class<Comparable<Object>> cClass = (Class<Comparable<Object>>) vClass;
Constructor<Comparable<Object>> constructor;
try {
constructor = cClass.getConstructor(constructorType);
if (!constructor.isAccessible())
AccessController.doPrivileged(new SetAccessibleAction(constructor));
synchronized (CoercingComparator.class) {
int top = coercers.length;
CoercingComparator<?>[] nc = new CoercingComparator<?>[top + 1];
System.arraycopy(coercers, 0, nc, 1, top);
CoercingComparator<V> cv = (CoercingComparator<V>) new FromStringCoercer<Comparable<Object>>(cClass, constructor);
nc[0] = cv;
coercers = nc;
return cv;
}
} catch (Exception e) {
//
}
}
throw new IllegalArgumentException("No comparator for " + vClass.getName()); //$NON-NLS-1$
}
protected IllegalArgumentException uncoercable(Object v) {
StringBuffer sb = new StringBuffer("Cannot coerce "); //$NON-NLS-1$
if (v instanceof String) {
sb.append('\'');
sb.append(v);
sb.append('\'');
} else if (v instanceof Number) {
sb.append("number "); //$NON-NLS-1$
sb.append(v);
} else {
sb.append("an object of instance "); //$NON-NLS-1$
sb.append(v.getClass().getName());
}
sb.append(" into a "); //$NON-NLS-1$
sb.append(getCoerceClass().getName());
return new IllegalArgumentException(sb.toString());
}
boolean canCoerceTo(Class<?> cls) {
return cls == getCoerceClass();
}
abstract T coerce(Object v);
abstract int compare(T o1, T o2);
abstract Class<T> getCoerceClass();
abstract int getCoercePrio();
}