/* * Copyright (c) 2006 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.internal.injection.constructor; import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; import javax.annotation.*; import static java.lang.reflect.Modifier.*; import mockit.internal.injection.*; import mockit.internal.state.*; import mockit.internal.util.*; import static mockit.internal.injection.InjectionPoint.*; import static mockit.internal.util.GeneratedClasses.*; public final class ConstructorSearch { private static final int CONSTRUCTOR_ACCESS = PUBLIC + PROTECTED + PRIVATE; @Nonnull private final InjectionState injectionState; @Nonnull private final Class<?> testedClass; @Nonnull private final String testedClassDesc; @Nonnull public List<InjectionPointProvider> parameterProviders; private final boolean withFullInjection; @Nullable private Constructor<?> constructor; @Nullable private StringBuilder searchResults; public ConstructorSearch( @Nonnull InjectionState injectionState, @Nonnull Class<?> testedClass, boolean withFullInjection) { this.injectionState = injectionState; this.testedClass = testedClass; Class<?> declaredClass = isGeneratedClass(testedClass.getName()) ? testedClass.getSuperclass() : testedClass; testedClassDesc = new ParameterNameExtractor().extractNames(declaredClass); parameterProviders = new ArrayList<InjectionPointProvider>(); this.withFullInjection = withFullInjection; } @Nullable public Constructor<?> findConstructorToUse() { constructor = null; Constructor<?>[] constructors = testedClass.getDeclaredConstructors(); if (!findSingleAnnotatedConstructor(constructors)) { findSatisfiedConstructorWithMostParameters(constructors); } return constructor; } private boolean findSingleAnnotatedConstructor(@Nonnull Constructor<?>[] constructors) { for (Constructor<?> c : constructors) { if (kindOfInjectionPoint(c) != KindOfInjectionPoint.NotAnnotated) { List<InjectionPointProvider> providersFound = findParameterProvidersForConstructor(c); if (providersFound != null) { parameterProviders = providersFound; constructor = c; } return true; } } return false; } private void findSatisfiedConstructorWithMostParameters(@Nonnull Constructor<?>[] constructors) { sortConstructorsWithMostAccessibleFirst(constructors); Constructor<?> unresolvedConstructor = null; List<InjectionPointProvider> incompleteProviders = null; for (Constructor<?> candidateConstructor : constructors) { List<InjectionPointProvider> providersFound = findParameterProvidersForConstructor(candidateConstructor); if (providersFound != null) { if (withFullInjection && containsUnresolvedProvider(providersFound)) { if ( unresolvedConstructor == null || isLargerConstructor(candidateConstructor, providersFound, unresolvedConstructor, incompleteProviders) ) { unresolvedConstructor = candidateConstructor; incompleteProviders = providersFound; } } else if ( constructor == null || isLargerConstructor(candidateConstructor, providersFound, constructor, parameterProviders) ) { constructor = candidateConstructor; parameterProviders = providersFound; } } } selectConstructorWithUnresolvedParameterIfMoreAccessible(unresolvedConstructor, incompleteProviders); } private static void sortConstructorsWithMostAccessibleFirst(@Nonnull Constructor<?>[] constructors) { if (constructors.length > 1) { Arrays.sort(constructors, CONSTRUCTOR_COMPARATOR); } } private static final Comparator<Constructor<?>> CONSTRUCTOR_COMPARATOR = new Comparator<Constructor<?>>() { @Override public int compare(Constructor<?> c1, Constructor<?> c2) { return compareAccessibility(c1, c2); } }; private static int compareAccessibility(@Nonnull Constructor<?> c1, @Nonnull Constructor<?> c2) { int m1 = getModifiers(c1); int m2 = getModifiers(c2); if (m1 == m2) return 0; if (m1 == PUBLIC) return -1; if (m2 == PUBLIC) return 1; if (m1 == PROTECTED) return -1; if (m2 == PROTECTED) return 1; if (m2 == PRIVATE) return -1; return 1; } private static boolean containsUnresolvedProvider(@Nonnull List<InjectionPointProvider> providersFound) { for (InjectionPointProvider provider : providersFound) { if (provider instanceof ConstructorParameter && provider.getValue(null) == null) { return true; } } return false; } private boolean isLargerConstructor( @Nonnull Constructor<?> candidateConstructor, @Nonnull List<InjectionPointProvider> providersFound, @Nonnull Constructor<?> previousSatisfiableConstructor, @Nonnull List<InjectionPointProvider> previousProviders) { return getModifiers(candidateConstructor) == getModifiers(previousSatisfiableConstructor) && providersFound.size() >= previousProviders.size(); } private static int getModifiers(@Nonnull Constructor<?> c) { return CONSTRUCTOR_ACCESS & c.getModifiers(); } @Nullable private List<InjectionPointProvider> findParameterProvidersForConstructor(@Nonnull Constructor<?> candidate) { Type[] parameterTypes = candidate.getGenericParameterTypes(); Annotation[][] parameterAnnotations = candidate.getParameterAnnotations(); int n = parameterTypes.length; List<InjectionPointProvider> providersFound = new ArrayList<InjectionPointProvider>(n); boolean varArgs = candidate.isVarArgs(); if (varArgs) { n--; } printCandidateConstructorNameIfRequested(candidate); String constructorDesc = "<init>" + mockit.external.asm.Type.getConstructorDescriptor(candidate); for (int i = 0; i < n; i++) { Type parameterType = parameterTypes[i]; injectionState.setTypeOfInjectionPoint(parameterType); String parameterName = ParameterNames.getName(testedClassDesc, constructorDesc, i); InjectionPointProvider provider = findOrCreateInjectionPointProvider(parameterType, parameterName, parameterAnnotations[i]); if (provider == null || providersFound.contains(provider)) { printParameterOfCandidateConstructorIfRequested(parameterName, provider); return null; } providersFound.add(provider); } if (varArgs) { Type parameterType = parameterTypes[n]; InjectionPointProvider injectable = hasInjectedValuesForVarargsParameter(parameterType); if (injectable != null) { providersFound.add(injectable); } } return providersFound; } @Nullable private InjectionPointProvider findOrCreateInjectionPointProvider( @Nonnull Type parameterType, @Nullable String parameterName, @Nonnull Annotation[] parameterAnnotations) { if (parameterName == null) { return null; } InjectionPointProvider provider = injectionState.getProviderByTypeAndOptionallyName(parameterName); if (provider == null && withFullInjection) { Object valueForParameter = injectionState.getTestedValueForConstructorParameter(parameterName); provider = new ConstructorParameter(parameterType, parameterAnnotations, parameterName, valueForParameter); } return provider; } @Nullable private InjectionPointProvider hasInjectedValuesForVarargsParameter(@Nonnull Type parameterType) { Type varargsElementType = getTypeOfInjectionPointFromVarargsParameter(parameterType); injectionState.setTypeOfInjectionPoint(varargsElementType); return injectionState.findNextInjectableForInjectionPoint(); } private void selectConstructorWithUnresolvedParameterIfMoreAccessible( @Nullable Constructor<?> unresolvedConstructor, List<InjectionPointProvider> incompleteProviders) { if ( unresolvedConstructor != null && (constructor == null || compareAccessibility(unresolvedConstructor, constructor) < 0) ) { constructor = unresolvedConstructor; parameterProviders = incompleteProviders; } } // Methods used only when no satisfiable constructor is found ////////////////////////////////////////////////////// @Nonnull public String getDescription() { searchResults = new StringBuilder(); findConstructorToUse(); String contents = searchResults.toString(); searchResults = null; return contents; } private void printCandidateConstructorNameIfRequested(@Nonnull Constructor<?> candidate) { if (searchResults != null) { String constructorDesc = candidate.toGenericString().replace("java.lang.", ""); searchResults.append("\r\n ").append(constructorDesc).append("\r\n"); } } private void printParameterOfCandidateConstructorIfRequested( @Nullable String parameterName, @Nullable InjectionPointProvider injectableFound) { if (searchResults != null) { searchResults.append(" disregarded because "); if (parameterName == null) { searchResults.append("parameter names are not available"); } else { searchResults.append("no injectable was found for parameter \"").append(parameterName).append('"'); if (injectableFound != null) { searchResults.append(" that hadn't been used already"); } } } } }