/* * Copyright 2006-2012 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * 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 com.amazon.carbonado.util; import java.util.Comparator; import org.cojen.classfile.TypeDesc; /** * Compares type conversions, finding the one that is nearest. * * @author Brian S O'Neill * @since 1.2 */ public class ConversionComparator implements Comparator<Class> { private final TypeDesc mFrom; public ConversionComparator(Class fromType) { mFrom = TypeDesc.forClass(fromType); } /** * Returns true if a coversion is possible to the given type. */ public boolean isConversionPossible(Class toType) { return isConversionPossible(mFrom, TypeDesc.forClass(toType)); } @SuppressWarnings("unchecked") private static boolean isConversionPossible(TypeDesc from, TypeDesc to) { if (from == to) { return true; } if (from.toPrimitiveType() != null && to.toPrimitiveType() != null) { from = from.toPrimitiveType(); to = to.toPrimitiveType(); } else { from = from.toObjectType(); to = to.toObjectType(); } switch (from.getTypeCode()) { case TypeDesc.OBJECT_CODE: default: return to.toClass().isAssignableFrom(from.toClass()); case TypeDesc.BOOLEAN_CODE: return to == TypeDesc.BOOLEAN; case TypeDesc.BYTE_CODE: return to == TypeDesc.BYTE || to == TypeDesc.SHORT || to == TypeDesc.INT || to == TypeDesc.LONG || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; case TypeDesc.SHORT_CODE: return to == TypeDesc.SHORT || to == TypeDesc.INT || to == TypeDesc.LONG || to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; case TypeDesc.CHAR_CODE: return to == TypeDesc.CHAR; case TypeDesc.INT_CODE: return to == TypeDesc.INT || to == TypeDesc.LONG || to == TypeDesc.DOUBLE; case TypeDesc.FLOAT_CODE: return to == TypeDesc.FLOAT || to == TypeDesc.DOUBLE; case TypeDesc.LONG_CODE: return to == TypeDesc.LONG; case TypeDesc.DOUBLE_CODE: return to == TypeDesc.DOUBLE; } } /** * Evaluates two types, to see which one is nearest to the from type. * Return {@literal <0} if "a" is nearest, 0 if both are equally good, * {@literal >0} if "b" is nearest. */ public int compare(Class toType_a, Class toType_b) { TypeDesc from = mFrom; TypeDesc a = TypeDesc.forClass(toType_a); TypeDesc b = TypeDesc.forClass(toType_b); if (from == a) { if (from == b) { return 0; } return -1; } else if (from == b) { return 1; } int result = compare(from, a, b); if (result != 0) { return result; } if (from.isPrimitive()) { // Try boxing. if (from.toObjectType() != null) { from = from.toObjectType(); return compare(from, a, b); } } else { // Try unboxing. if (from.toPrimitiveType() != null) { from = from.toPrimitiveType(); result = compare(from, a, b); if (result != 0) { return result; } // Try boxing back up. Test by unboxing 'to' types. if (!toType_a.isPrimitive() && a.toPrimitiveType() != null) { a = a.toPrimitiveType(); } if (!toType_b.isPrimitive() && b.toPrimitiveType() != null) { b = b.toPrimitiveType(); } return compare(from, a, b); } } return 0; } private static int compare(TypeDesc from, TypeDesc a, TypeDesc b) { if (isConversionPossible(from, a)) { if (isConversionPossible(from, b)) { if (from.isPrimitive()) { if (a.isPrimitive()) { if (b.isPrimitive()) { // Choose the one with the least amount of widening. return primitiveWidth(a) - primitiveWidth(b); } else { return -1; } } else if (b.isPrimitive()) { return 1; } } else { // Choose the one with the shortest distance up the class // hierarchy. Class fromClass = from.toClass(); if (!fromClass.isInterface()) { if (a.toClass().isInterface()) { if (!b.toClass().isInterface()) { return -1; } } else if (b.toClass().isInterface()) { return 1; } else { return distance(fromClass, a.toClass()) - distance(fromClass, b.toClass()); } } } } else { return -1; } } else if (isConversionPossible(from, b)) { return 1; } return 0; } // 1 = boolean, 2 = byte, 3 = short, 4 = char, 5 = int, 6 = float, 7 = long, 8 = double private static int primitiveWidth(TypeDesc type) { switch (type.getTypeCode()) { default: return 0; case TypeDesc.BOOLEAN_CODE: return 1; case TypeDesc.BYTE_CODE: return 2; case TypeDesc.SHORT_CODE: return 3; case TypeDesc.CHAR_CODE: return 4; case TypeDesc.INT_CODE: return 5; case TypeDesc.FLOAT_CODE: return 6; case TypeDesc.LONG_CODE: return 7; case TypeDesc.DOUBLE_CODE: return 8; } } private static int distance(Class from, Class to) { int distance = 0; while (from != to) { from = from.getSuperclass(); if (from == null) { return Integer.MAX_VALUE; } distance++; } return distance; } }