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}