001/* 002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others 003 * 004 * This program and the accompanying materials are made available under the 005 * terms of the Eclipse Public License 2.0 which is available at 006 * http://www.eclipse.org/legal/epl-2.0. 007 * 008 * SPDX-License-Identifier: EPL-2.0 009 */ 010 011package org.eclipse.golo.runtime; 012 013import java.lang.reflect.Constructor; 014import java.lang.reflect.Method; 015import java.lang.reflect.Modifier; 016import java.util.HashMap; 017import java.util.Map; 018import java.util.List; 019 020import static org.eclipse.golo.runtime.DecoratorsHelper.isMethodDecorated; 021import static java.util.Arrays.copyOfRange; 022import static gololang.Predefined.isClosure; 023 024public final class TypeMatching { 025 026 private TypeMatching() { 027 throw new UnsupportedOperationException("Don't instantiate utility classes"); 028 } 029 030 private static final Map<Class<?>, Class<?>> PRIMITIVE_MAP = new HashMap<Class<?>, Class<?>>() { 031 { 032 put(byte.class, Byte.class); 033 put(short.class, Short.class); 034 put(char.class, Character.class); 035 put(int.class, Integer.class); 036 put(long.class, Long.class); 037 put(float.class, Float.class); 038 put(double.class, Double.class); 039 put(boolean.class, Boolean.class); 040 } 041 }; 042 043 private static final List<Class<?>> NUMBERS = java.util.Arrays.asList( 044 Short.class, 045 Character.class, 046 Integer.class, 047 Long.class, 048 Float.class, 049 Double.class); 050 051 public static boolean canAssign(Class<?>[] types, Object[] arguments, boolean varArgs) { 052 if (types.length == 0 || arguments.length == 0) { 053 return true; 054 } 055 for (int i = 0; i < types.length - 1; i++) { 056 if (!valueAndTypeMatch(types[i], arguments[i])) { 057 return false; 058 } 059 } 060 final int last = types.length - 1; 061 if (varArgs && arguments.length == last) { 062 return true; 063 } 064 if (last >= arguments.length) { 065 return false; 066 } 067 if (varArgs && !(arguments[last] instanceof Object[])) { 068 return valueAndTypeMatch(types[last].getComponentType(), arguments[last]); 069 } 070 return valueAndTypeMatch(types[last], arguments[last]); 071 } 072 073 private static boolean valueAndTypeMatch(Class<?> type, Object value) { 074 if (type == null) { 075 return false; 076 } 077 return primitiveCompatible(type, value) 078 || (type.isInstance(value) 079 || value == null 080 || samAssignment(type, value) 081 || functionalInterfaceAssignment(type, value)); 082 } 083 084 public static boolean functionalInterfaceAssignment(Class<?> type, Object value) { 085 return isClosure(value) && isFunctionalInterface(type); 086 } 087 088 public static boolean samAssignment(Class<?> type, Object value) { 089 return isClosure(value) && isSAM(type); 090 } 091 092 public static boolean isSAM(Class<?> type) { 093 return type.isInterface() && (type.getMethods().length == 1); 094 } 095 096 public static boolean isFunctionalInterface(Class<?> type) { 097 return type.isAnnotationPresent(FunctionalInterface.class); 098 } 099 100 private static boolean primitiveCompatible(Class<?> type, Object value) { 101 if (!type.isPrimitive() || value == null) { 102 return false; 103 } 104 Class<?> boxedType = PRIMITIVE_MAP.get(type); 105 Class<?> valueType = value.getClass(); 106 if (boxedType == valueType) { 107 return true; 108 } 109 if (Number.class.isAssignableFrom(boxedType) && Number.class.isAssignableFrom(valueType)) { 110 return NUMBERS.indexOf(boxedType) > NUMBERS.indexOf(valueType); 111 } 112 return false; 113 } 114 115 /** 116 * Compare two types arrays for type compatibility, using lexicographic order. 117 */ 118 public static int compareTypes(Class<?>[] types1, Class<?>[] types2) { 119 if (types1.length != types2.length) { 120 return Integer.compare(types1.length, types2.length); 121 } 122 for (int i = 0; i < types1.length; i++) { 123 int cmp = compareSubstituable(types1[i], types2[i]); 124 if (cmp != 0) { 125 return cmp; 126 } 127 } 128 return 0; 129 } 130 131 /** 132 * Compare two type with a substituability relation (subtyping). 133 * 134 * <p>If the two type are not comparable, {@code 0} is returned so that the order is not changed in a stable sort. 135 */ 136 public static int compareSubstituable(Class<?> type1, Class<?> type2) { 137 Class<?> boxed1 = boxed(type1); 138 Class<?> boxed2 = boxed(type2); 139 if (boxed1 == boxed2) { 140 return 0; 141 } 142 if (boxed1.isAssignableFrom(boxed2)) { 143 return 1; 144 } 145 if (boxed2.isAssignableFrom(boxed1)) { 146 return -1; 147 } 148 if (Number.class.isAssignableFrom(boxed1) && Number.class.isAssignableFrom(boxed2)) { 149 return Integer.compare(NUMBERS.indexOf(boxed1), NUMBERS.indexOf(boxed2)); 150 } 151 return 0; 152 } 153 154 /** 155 * @return the boxed version of the given type, and class itself otherwise. 156 */ 157 public static Class<?> boxed(Class<?> t) { 158 if (!t.isPrimitive()) { 159 return t; 160 } 161 return PRIMITIVE_MAP.get(t); 162 } 163 164 165 public static boolean isLastArgumentAnArray(int index, Object[] args) { 166 return index > 0 && args.length == index && args[index - 1] instanceof Object[]; 167 } 168 169 public static boolean argumentsNumberMatches(int paramsNumber, int argsNumber, boolean isVarArgs) { 170 return argsNumber < 0 171 || (!isVarArgs && paramsNumber == argsNumber) 172 || (isVarArgs && argsNumber >= paramsNumber - 1); 173 } 174 175 public static boolean argumentsNumberMatches(Method method, int argsNumber) { 176 return argumentsNumberMatches(method.getParameterCount(), argsNumber, method.isVarArgs()); 177 } 178 179 public static boolean argumentsMatch(Method method, Object[] arguments) { 180 return argumentsMatch(method, arguments, method.isVarArgs()); 181 } 182 183 public static boolean argumentsMatch(Method method, Object[] arguments, boolean varargs) { 184 Object[] args = Modifier.isStatic(method.getModifiers()) 185 ? arguments 186 : copyOfRange(arguments, 1, arguments.length); 187 return 188 isMethodDecorated(method) 189 || (argumentsNumberMatches(method.getParameterCount(), args.length, varargs) 190 && canAssign(method.getParameterTypes(), args, varargs)); 191 } 192 193 public static boolean argumentsMatch(Constructor<?> constructor, Object[] arguments) { 194 return 195 argumentsNumberMatches(constructor.getParameterCount(), arguments.length, constructor.isVarArgs()) 196 && canAssign(constructor.getParameterTypes(), arguments, constructor.isVarArgs()); 197 } 198 199 public static boolean returnsValue(Method m) { 200 return !(m.getReturnType().equals(void.class) || m.getReturnType().equals(Void.class)); 201 } 202}