001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package org.wpilib.util; 006 007import java.lang.reflect.Constructor; 008import java.util.List; 009import java.util.Optional; 010 011/** 012 * Utility class to find the best matching constructor for a given set of parameter types. The 013 * constructor must have parameter types that are assignable from the given parameter types, and the 014 * parameter types must not be assignable to each other. If multiple constructors match, the one 015 * with the most specific parameter types is chosen. If there is still a tie, the one with the most 016 * specific first parameter type is chosen, then the second parameter type, and so on. 017 * 018 * @param <T> the type of the class to find the constructor for 019 */ 020public class ConstructorMatch<T> { 021 private final Constructor<T> m_constructor; 022 private final List<Class<?>> m_parameterTypes; 023 024 /** 025 * Constructs a ConstructorMatch with the given constructor and parameter types. The parameter 026 * types must not be assignable to each other. 027 * 028 * @param constructor the constructor to match 029 * @param parameterTypes the parameter types for the constructor 030 */ 031 public ConstructorMatch(Constructor<T> constructor, Class<?>... parameterTypes) { 032 m_constructor = constructor; 033 m_parameterTypes = List.of(parameterTypes); 034 if (!isValidParameterPack(parameterTypes)) { 035 throw new IllegalArgumentException("Parameter types must not be assignable to each other"); 036 } 037 } 038 039 private static boolean isValidParameterPack(Class<?>... types) { 040 // Verify that all of the parameter types are not assignable to each other 041 for (int i = 0; i < types.length; i++) { 042 // Don't allow object parameters, as they would match any parameter type 043 // and prevent more specific matches from being found 044 if (types[i].equals(Object.class)) { 045 return false; 046 } 047 048 for (int j = i + 1; j < types.length; j++) { 049 if (types[i].isAssignableFrom(types[j]) || types[j].isAssignableFrom(types[i])) { 050 return false; 051 } 052 } 053 } 054 return true; 055 } 056 057 /** 058 * Creates a new instance of the constructor's class using the given arguments. The arguments must 059 * match the parameter types of the constructor, and must not be assignable to each other. The 060 * order of the arguments does matter, as they will be matched to the constructor parameter types 061 * in order. 062 * 063 * @param args the arguments to pass to the constructor 064 * @return a new instance of the constructor's class 065 * @throws ReflectiveOperationException if the constructor cannot be invoked 066 */ 067 public T newInstance(Object... args) throws ReflectiveOperationException { 068 Object[] parameterArgs = new Object[m_parameterTypes.size()]; 069 // Find the incoming argument that matches each parameter type 070 for (int i = 0; i < m_parameterTypes.size(); i++) { 071 boolean found = false; 072 for (Object arg : args) { 073 if (m_parameterTypes.get(i).isAssignableFrom(arg.getClass())) { 074 parameterArgs[i] = arg; 075 found = true; 076 break; 077 } 078 } 079 if (!found) { 080 throw new IllegalArgumentException( 081 "No argument found for parameter type " + m_parameterTypes.get(i)); 082 } 083 } 084 return m_constructor.newInstance(parameterArgs); 085 } 086 087 /** 088 * Finds the best matching constructor for the given class and parameter types. The constructor 089 * must have parameter types that are assignable from the given parameter types, and the parameter 090 * types must not be assignable to each other. If multiple constructors match, the one with the 091 * most specific parameter types is chosen. If there is still a tie, the one with the most 092 * specific first parameter type is chosen, then the second parameter type, and so on. The order 093 * of the parameter types does matter, as they will be matched to the constructor parameter types 094 * in order. 095 * 096 * @param <T> the type of the class to find the constructor for 097 * @param clazz the class to find the constructor for 098 * @param parameterTypes the parameter types to match 099 * @return an Optional containing the best matching ConstructorMatch, or empty if no match is 100 * found 101 */ 102 public static <T> Optional<ConstructorMatch<T>> findBestConstructor( 103 Class<T> clazz, Class<?>... parameterTypes) { 104 if (!isValidParameterPack(parameterTypes)) { 105 return Optional.empty(); 106 } 107 Constructor<T> bestCtor = null; 108 Class<?>[] bestParameterTypes = new Class<?>[parameterTypes.length]; 109 @SuppressWarnings("unchecked") 110 Constructor<T>[] constructors = (Constructor<T>[]) clazz.getConstructors(); 111 for (Constructor<T> constructor : constructors) { 112 Class<?>[] ctorParameterTypes = constructor.getParameterTypes(); 113 if (ctorParameterTypes.length != parameterTypes.length) { 114 continue; 115 } 116 boolean matches = true; 117 for (int i = 0; i < parameterTypes.length; i++) { 118 // Don't allow object parameters, as they would match any parameter type and 119 // prevent more specific matches from being found 120 if (ctorParameterTypes[i].equals(Object.class)) { 121 matches = false; 122 break; 123 } 124 if (!ctorParameterTypes[i].isAssignableFrom(parameterTypes[i])) { 125 matches = false; 126 break; 127 } 128 } 129 if (!matches) { 130 continue; 131 } 132 boolean better = false; 133 if (bestCtor == null) { 134 better = true; 135 } else { 136 // Check if this constructor is more specific than the best one found so far 137 // Order by parameter order so that if one constructor has a more specific 138 // parameter type for the first parameter, it is preferred over a constructor 139 // that has a more specific parameter type for the second parameter 140 for (int i = 0; i < parameterTypes.length; i++) { 141 if (ctorParameterTypes[i] != bestParameterTypes[i]) { 142 if (bestParameterTypes[i].isAssignableFrom(ctorParameterTypes[i])) { 143 better = true; 144 } 145 break; 146 } 147 } 148 } 149 if (better) { 150 bestCtor = constructor; 151 System.arraycopy(ctorParameterTypes, 0, bestParameterTypes, 0, parameterTypes.length); 152 } 153 } 154 return bestCtor == null 155 ? Optional.empty() 156 : Optional.of(new ConstructorMatch<>(bestCtor, bestParameterTypes)); 157 } 158 159 /** 160 * Returns the constructor that was matched. 161 * 162 * @return the constructor that was matched 163 */ 164 public Constructor<T> getConstructor() { 165 return m_constructor; 166 } 167 168 /** 169 * Returns the parameter types for the constructor. 170 * 171 * @return the parameter types for the constructor 172 */ 173 public List<Class<?>> getParameterTypes() { 174 return m_parameterTypes; 175 } 176}