/******************************************************************************* * Copyright (c) 2010 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 new Integer(((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(); }