package net.bytebuddy.implementation.bind; import lombok.EqualsAndHashCode; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.ParameterList; import net.bytebuddy.description.type.TypeDescription; /** * Implementation of an * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver} * that resolves two conflicting bindings by considering most-specific types of target method parameters in the same manner * as the Java compiler resolves bindings of overloaded method. * <p> </p> * This ambiguity resolver: * <ol> * <li>Checks for each parameter of the source method if a one-to-one parameter binding to both of the target methods exist.</li> * <li>If any of the source method parameters were bound one-to-one to both target methods, the method with the most specific * type is considered as dominant.</li> * <li>If this result is dominant for both the left and the right target method, this resolver will consider the binding as * ambiguous.</li> * <li>If none of the methods is dominant and if the comparison did not result in an ambigous resolution, the method that * consists of the most one-to-one parameter bindings is considered dominant.</li> * </ol> * Primitive types are considered dominant in the same manner as by the Java compiler. * <p> </p> * For example: If a source method only parameter was successfully bound one-to-one to the only parameters of the target * methods {@code foo(Object)} and {@code bar(String)}, this ambiguity resolver will detect that the {@code String} type * is more specific than the {@code Object} type and determine {@code bar(String)} as the dominant binding. */ public enum ArgumentTypeResolver implements MethodDelegationBinder.AmbiguityResolver { /** * The singleton instance. */ INSTANCE; /** * Resolves two bindings by comparing their binding of similar arguments and determining their most specific types. * * @param sourceParameterType The parameter type of the source method * @param leftParameterIndex The index of the parameter of the left method. * @param left The left method's parameter binding. * @param rightParameterIndex The index of the parameter of the right method. * @param right The right method's parameter binding. * @return A resolution according to the given parameters. */ private static Resolution resolveRivalBinding(TypeDescription sourceParameterType, int leftParameterIndex, MethodDelegationBinder.MethodBinding left, int rightParameterIndex, MethodDelegationBinder.MethodBinding right) { TypeDescription leftParameterType = left.getTarget().getParameters().get(leftParameterIndex).getType().asErasure(); TypeDescription rightParameterType = right.getTarget().getParameters().get(rightParameterIndex).getType().asErasure(); if (!leftParameterType.equals(rightParameterType)) { if (leftParameterType.isPrimitive() && rightParameterType.isPrimitive()) { return PrimitiveTypePrecedence.forPrimitive(leftParameterType) .resolve(PrimitiveTypePrecedence.forPrimitive(rightParameterType)); } else if (leftParameterType.isPrimitive() /* && !rightParameterType.isPrimitive() */) { return sourceParameterType.isPrimitive() ? Resolution.LEFT : Resolution.RIGHT; } else if (/* !leftParameterType.isPrimitive() && */ rightParameterType.isPrimitive()) { return sourceParameterType.isPrimitive() ? Resolution.RIGHT : Resolution.LEFT; } else { // Note that leftParameterType != rightParameterType, thus both cannot be true. if (leftParameterType.isAssignableFrom(rightParameterType)) { return Resolution.RIGHT; } else if (rightParameterType.isAssignableFrom(leftParameterType)) { return Resolution.LEFT; } else { return Resolution.AMBIGUOUS; } } } else { return Resolution.UNKNOWN; } } /** * Resolves the most specific method by their score. A method's score is calculated by the absolute number of * parameters that were bound by using an explicit {@link net.bytebuddy.implementation.bind.annotation.Argument} * annotation. * * @param boundParameterScore The difference of the scores of the left and the right method. * @return A resolution according to this score. */ private static Resolution resolveByScore(int boundParameterScore) { if (boundParameterScore == 0) { return Resolution.AMBIGUOUS; } else if (boundParameterScore > 0) { return Resolution.LEFT; } else /* difference < 0*/ { return Resolution.RIGHT; } } @Override public Resolution resolve(MethodDescription source, MethodDelegationBinder.MethodBinding left, MethodDelegationBinder.MethodBinding right) { Resolution resolution = Resolution.UNKNOWN; ParameterList<?> sourceParameters = source.getParameters(); int leftExtra = 0, rightExtra = 0; for (int sourceParameterIndex = 0; sourceParameterIndex < sourceParameters.size(); sourceParameterIndex++) { ParameterIndexToken parameterIndexToken = new ParameterIndexToken(sourceParameterIndex); Integer leftParameterIndex = left.getTargetParameterIndex(parameterIndexToken); Integer rightParameterIndex = right.getTargetParameterIndex(parameterIndexToken); if (leftParameterIndex != null && rightParameterIndex != null) { resolution = resolution.merge(resolveRivalBinding(sourceParameters.get(sourceParameterIndex).getType().asErasure(), leftParameterIndex, left, rightParameterIndex, right)); } else if (leftParameterIndex != null /* && rightParameterIndex == null */) { leftExtra++; } else if (/*leftParameterIndex == null && */ rightParameterIndex != null) { rightExtra++; } } return resolution == Resolution.UNKNOWN ? resolveByScore(leftExtra - rightExtra) : resolution; } /** * A representation of the precedence of a most specific primitive type in the Java programming language. */ protected enum PrimitiveTypePrecedence { /** * The precedence of the {@code boolean} type. */ BOOLEAN(0), /** * The precedence of the {@code byte} type. */ BYTE(1), /** * The precedence of the {@code short} type. */ SHORT(2), /** * The precedence of the {@code int} type. */ INTEGER(3), /** * The precedence of the {@code char} type. */ CHARACTER(4), /** * The precedence of the {@code long} type. */ LONG(5), /** * The precedence of the {@code float} type. */ FLOAT(6), /** * The precedence of the {@code double} type. */ DOUBLE(7); /** * A score representing the precedence where a higher score represents a less specific type. */ private final int score; /** * Creates a new primitive type precedence. * * @param score A score representing the precedence where a higher score represents a less specific type. */ PrimitiveTypePrecedence(int score) { this.score = score; } /** * Locates the primitive type precedence for a given type. * * @param typeDescription The non-void, primitive type for which the precedence should be located. * @return The corresponding primitive type precedence. */ public static PrimitiveTypePrecedence forPrimitive(TypeDescription typeDescription) { if (typeDescription.represents(boolean.class)) { return BOOLEAN; } else if (typeDescription.represents(byte.class)) { return BYTE; } else if (typeDescription.represents(short.class)) { return SHORT; } else if (typeDescription.represents(int.class)) { return INTEGER; } else if (typeDescription.represents(char.class)) { return CHARACTER; } else if (typeDescription.represents(long.class)) { return LONG; } else if (typeDescription.represents(float.class)) { return FLOAT; } else if (typeDescription.represents(double.class)) { return DOUBLE; } else { throw new IllegalArgumentException("Not a non-void, primitive type " + typeDescription); } } /** * Resolves the least specific type of two primitive type precedence with this instance representing a * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#LEFT} * resolution and the argument type representing the * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#RIGHT} * resolution. * * @param right Another primitive type precedence against which this precedence should be resolved. * @return The resolution of */ public Resolution resolve(PrimitiveTypePrecedence right) { if (score - right.score == 0) { return Resolution.UNKNOWN; } else if (score - right.score > 0) { return Resolution.RIGHT; } else /* score - right.score < 0 */ { return Resolution.LEFT; } } } /** * This token is used to mark a one-to-one binding of a source method parameter to a target method parameter. * * @see net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding#getTargetParameterIndex(Object) */ @EqualsAndHashCode public static class ParameterIndexToken { /** * The parameter index that is represented by this token. */ private final int parameterIndex; /** * Create a parameter index token for a given parameter of the source method. * * @param parameterIndex The parameter index of the source method which is mapped to a target method parameter. */ public ParameterIndexToken(int parameterIndex) { this.parameterIndex = parameterIndex; } } }