All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.spi.InjectionPoint Maven / Gradle / Ivy

package com.google.inject.spi;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.inject.ConfigurationException;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.DeclaredMembers;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ErrorsException;
import com.google.inject.internal.Nullability;
import com.google.inject.internal.util.Classes;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.google.inject.internal.MoreTypes.getRawType;

/**
 * A constructor, field or method that can receive injections. Typically this is a member with the
 * {@literal @}{@link Inject} annotation. For non-private, no argument constructors, the member may
 * omit the annotation.
 */
public final class InjectionPoint {

    private static final Logger logger = Logger.getLogger(InjectionPoint.class.getName());

    private final boolean optional;
    private final Member member;
    private final TypeLiteral declaringType;
    private final ImmutableList> dependencies;

    InjectionPoint(TypeLiteral declaringType, Method method, boolean optional) {
        this.member = method;
        this.declaringType = declaringType;
        this.optional = optional;
        this.dependencies = forMember(method, declaringType, method.getParameterAnnotations());
    }

    InjectionPoint(TypeLiteral declaringType, Constructor constructor) {
        this.member = constructor;
        this.declaringType = declaringType;
        this.optional = false;
        this.dependencies = forMember(
                constructor, declaringType, constructor.getParameterAnnotations());
    }

    InjectionPoint(TypeLiteral declaringType, Field field, boolean optional) {
        this.member = field;
        this.declaringType = declaringType;
        this.optional = optional;

        Annotation[] annotations = field.getAnnotations();

        Errors errors = new Errors(field);
        Key key = null;
        try {
            key = Annotations.getKey(declaringType.getFieldType(field), field, annotations, errors);
        } catch (ConfigurationException e) {
            errors.merge(e.getErrorMessages());
        } catch (ErrorsException e) {
            errors.merge(e.getErrors());
        }
        errors.throwConfigurationExceptionIfErrorsExist();

        this.dependencies = ImmutableList.of(
                newDependency(key, Nullability.allowsNull(annotations), -1));
    }

    /**
     * Returns a new injection point for the specified constructor. If the declaring type of {@code
     * constructor} is parameterized (such as {@code List}), prefer the overload that includes a
     * type literal.
     *
     * @param constructor any single constructor present on {@code type}.
     * @since 3.0
     */
    public static  InjectionPoint forConstructor(Constructor constructor) {
        return new InjectionPoint(TypeLiteral.get(constructor.getDeclaringClass()), constructor);
    }

    /**
     * Returns a new injection point for the specified constructor of {@code type}.
     *
     * @param constructor any single constructor present on {@code type}.
     * @param type        the concrete type that defines {@code constructor}.
     */
    public static  InjectionPoint forConstructor(Constructor constructor, TypeLiteral type) {
        if (type.getRawType() != constructor.getDeclaringClass()) {
            new Errors(type)
                    .constructorNotDefinedByType(constructor, type)
                    .throwConfigurationExceptionIfErrorsExist();
        }

        return new InjectionPoint(type, constructor);
    }

    /**
     * Returns a new injection point for the injectable constructor of {@code type}.
     *
     * @param type a concrete type with exactly one constructor annotated {@literal @}{@link Inject},
     *             or a no-arguments constructor that is not private.
     * @throws ConfigurationException if there is no injectable constructor, more than one injectable
     *                                constructor, or if parameters of the injectable constructor are malformed, such as
     *                                a
     *                                parameter with multiple binding annotations.
     */
    public static InjectionPoint forConstructorOf(TypeLiteral type) {
        Class rawType = getRawType(type.getType());
        Errors errors = new Errors(rawType);

        Constructor injectableConstructor = null;
        for (Constructor constructor : rawType.getDeclaredConstructors()) {

            boolean optional;
            Inject guiceInject = constructor.getAnnotation(Inject.class);
            if (guiceInject == null) {
                javax.inject.Inject javaxInject = constructor.getAnnotation(javax.inject.Inject.class);
                if (javaxInject == null) {
                    continue;
                }
                optional = false;
            } else {
                optional = guiceInject.optional();
            }

            if (optional) {
                errors.optionalConstructor(constructor);
            }

            if (injectableConstructor != null) {
                errors.tooManyConstructors(rawType);
            }

            injectableConstructor = constructor;
            checkForMisplacedBindingAnnotations(injectableConstructor, errors);
        }

        errors.throwConfigurationExceptionIfErrorsExist();

        if (injectableConstructor != null) {
            return new InjectionPoint(type, injectableConstructor);
        }

        // If no annotated constructor is found, look for a no-arg constructor instead.
        try {
            Constructor noArgConstructor = rawType.getDeclaredConstructor();

            // Disallow private constructors on non-private classes (unless they have @Inject)
            if (Modifier.isPrivate(noArgConstructor.getModifiers())
                    && !Modifier.isPrivate(rawType.getModifiers())) {
                errors.missingConstructor(type);
                throw new ConfigurationException(errors.getMessages());
            }

            checkForMisplacedBindingAnnotations(noArgConstructor, errors);
            return new InjectionPoint(type, noArgConstructor);
        } catch (NoSuchMethodException e) {
            errors.missingConstructor(type);
            throw new ConfigurationException(errors.getMessages());
        }
    }

    /**
     * Returns a new injection point for the injectable constructor of {@code type}.
     *
     * @param type a concrete type with exactly one constructor annotated {@literal @}{@link Inject},
     *             or a no-arguments constructor that is not private.
     * @throws ConfigurationException if there is no injectable constructor, more than one injectable
     *                                constructor, or if parameters of the injectable constructor are malformed, such as
     *                                a
     *                                parameter with multiple binding annotations.
     */
    public static InjectionPoint forConstructorOf(Class type) {
        return forConstructorOf(TypeLiteral.get(type));
    }

    /**
     * Returns a new injection point for the specified method of {@code type}.
     * This is useful for extensions that need to build dependency graphs from
     * arbitrary methods.
     *
     * @param method any single method present on {@code type}.
     * @param type   the concrete type that defines {@code method}.
     */
    public static  InjectionPoint forMethod(Method method, TypeLiteral type) {
        return new InjectionPoint(type, method, false);
    }

    /**
     * Returns all static method and field injection points on {@code type}.
     *
     * @return a possibly empty set of injection points. The set has a specified iteration order. All
     * fields are returned and then all methods. Within the fields, supertype fields are returned
     * before subtype fields. Similarly, supertype methods are returned before subtype methods.
     * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as
     *                                a field with multiple binding annotations. The exception's {@link
     *                                ConfigurationException#getPartialValue() partial value} is a {@code
     *                                Set}
     *                                of the valid injection points.
     */
    public static Set forStaticMethodsAndFields(TypeLiteral type) {
        Errors errors = new Errors();

        Set result;

        if (type.getRawType().isInterface()) {
            errors.staticInjectionOnInterface(type.getRawType());
            result = null;
        } else {
            result = getInjectionPoints(type, true, errors);
        }

        if (errors.hasErrors()) {
            throw new ConfigurationException(errors.getMessages()).withPartialValue(result);
        }
        return result;
    }

    /**
     * Returns all static method and field injection points on {@code type}.
     *
     * @return a possibly empty set of injection points. The set has a specified iteration order. All
     * fields are returned and then all methods. Within the fields, supertype fields are returned
     * before subtype fields. Similarly, supertype methods are returned before subtype methods.
     * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as
     *                                a field with multiple binding annotations. The exception's {@link
     *                                ConfigurationException#getPartialValue() partial value} is a {@code
     *                                Set}
     *                                of the valid injection points.
     */
    public static Set forStaticMethodsAndFields(Class type) {
        return forStaticMethodsAndFields(TypeLiteral.get(type));
    }

    /**
     * Returns all instance method and field injection points on {@code type}.
     *
     * @return a possibly empty set of injection points. The set has a specified iteration order. All
     * fields are returned and then all methods. Within the fields, supertype fields are returned
     * before subtype fields. Similarly, supertype methods are returned before subtype methods.
     * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as
     *                                a field with multiple binding annotations. The exception's {@link
     *                                ConfigurationException#getPartialValue() partial value} is a {@code
     *                                Set}
     *                                of the valid injection points.
     */
    public static Set forInstanceMethodsAndFields(TypeLiteral type) {
        Errors errors = new Errors();
        Set result = getInjectionPoints(type, false, errors);
        if (errors.hasErrors()) {
            throw new ConfigurationException(errors.getMessages()).withPartialValue(result);
        }
        return result;
    }

    /**
     * Returns all instance method and field injection points on {@code type}.
     *
     * @return a possibly empty set of injection points. The set has a specified iteration order. All
     * fields are returned and then all methods. Within the fields, supertype fields are returned
     * before subtype fields. Similarly, supertype methods are returned before subtype methods.
     * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as
     *                                a field with multiple binding annotations. The exception's {@link
     *                                ConfigurationException#getPartialValue() partial value} is a {@code
     *                                Set}
     *                                of the valid injection points.
     */
    public static Set forInstanceMethodsAndFields(Class type) {
        return forInstanceMethodsAndFields(TypeLiteral.get(type));
    }

    /**
     * Returns true if the binding annotation is in the wrong place.
     */
    private static boolean checkForMisplacedBindingAnnotations(Member member, Errors errors) {
        Annotation misplacedBindingAnnotation = Annotations.findBindingAnnotation(
                errors, member, ((AnnotatedElement) member).getAnnotations());
        if (misplacedBindingAnnotation == null) {
            return false;
        }

        // don't warn about misplaced binding annotations on methods when there's a field with the same
        // name. In Scala, fields always get accessor methods (that we need to ignore). See bug 242.
        if (member instanceof Method) {
            try {
                if (member.getDeclaringClass().getDeclaredField(member.getName()) != null) {
                    return false;
                }
            } catch (NoSuchFieldException ignore) {
            }
        }

        errors.misplacedBindingAnnotation(member, misplacedBindingAnnotation);
        return true;
    }

    static Annotation getAtInject(AnnotatedElement member) {
        Annotation a = member.getAnnotation(javax.inject.Inject.class);
        return a == null ? member.getAnnotation(Inject.class) : a;
    }

    /**
     * Returns an ordered, immutable set of injection points for the given type. Members in
     * superclasses come before members in subclasses. Within a class, fields come before methods.
     * Overridden methods are filtered out.
     *
     * @param statics true is this method should return static members, false for instance members
     * @param errors  used to record errors
     */
    private static Set getInjectionPoints(final TypeLiteral type,
                                                          boolean statics, Errors errors) {
        InjectableMembers injectableMembers = new InjectableMembers();
        OverrideIndex overrideIndex = null;

        List> hierarchy = hierarchyFor(type);
        int topIndex = hierarchy.size() - 1;
        for (int i = topIndex; i >= 0; i--) {
            if (overrideIndex != null && i < topIndex) {
                // Knowing the position within the hierarchy helps us make optimizations.
                if (i == 0) {
                    overrideIndex.position = Position.BOTTOM;
                } else {
                    overrideIndex.position = Position.MIDDLE;
                }
            }

            TypeLiteral current = hierarchy.get(i);

            for (Field field : getDeclaredFields(current)) {
                if (Modifier.isStatic(field.getModifiers()) == statics) {
                    Annotation atInject = getAtInject(field);
                    if (atInject != null) {
                        InjectableField injectableField = new InjectableField(current, field, atInject);
                        if (injectableField.jsr330 && Modifier.isFinal(field.getModifiers())) {
                            errors.cannotInjectFinalField(field);
                        }
                        injectableMembers.add(injectableField);
                    }
                }
            }

            for (Method method : getDeclaredMethods(current)) {
                if (isEligibleForInjection(method, statics)) {
                    Annotation atInject = getAtInject(method);
                    if (atInject != null) {
                        InjectableMethod injectableMethod = new InjectableMethod(current, method, atInject);
                        if (checkForMisplacedBindingAnnotations(method, errors) ||
                                !isValidMethod(injectableMethod, errors)) {
                            if (overrideIndex != null) {
                                boolean removed = overrideIndex.removeIfOverriddenBy(method, false, injectableMethod);
                                if (removed) {
                                    logger.log(
                                            Level.WARNING,
                                            "Method " + method + " is not a valid injectable method ("
                                                    + "because it either has misplaced binding annotations "
                                                    + "or specifies type parameters) but is overriding a method that is "
                                                    + "valid. Because it is not valid, the method will not be injected. "
                                                    + "To fix this, make the method a valid injectable method.");
                                }
                            }
                            continue;
                        }
                        if (statics) {
                            injectableMembers.add(injectableMethod);
                        } else {
                            if (overrideIndex == null) {
                                /*
                                 * Creating the override index lazily means that the first type in the hierarchy
                                 * with injectable methods (not necessarily the top most type) will be treated as
                                 * the TOP position and will enjoy the same optimizations (no checks for overridden
                                 * methods, etc.).
                                 */
                                overrideIndex = new OverrideIndex(injectableMembers);
                            } else {
                                // Forcibly remove the overriden method, otherwise we'll inject
                                // it twice.
                                overrideIndex.removeIfOverriddenBy(method, true, injectableMethod);
                            }
                            overrideIndex.add(injectableMethod);
                        }
                    } else {
                        if (overrideIndex != null) {
                            overrideIndex.removeIfOverriddenBy(method, false, null);
                        }
                    }
                }
            }
        }

        if (injectableMembers.isEmpty()) {
            return Collections.emptySet();
        }

        ImmutableSet.Builder builder = ImmutableSet.builder();
        for (InjectableMember im = injectableMembers.head; im != null; im = im.next) {
            try {
                builder.add(im.toInjectionPoint());
            } catch (ConfigurationException ignorable) {
                if (!im.optional) {
                    errors.merge(ignorable.getErrorMessages());
                }
            }
        }
        return builder.build();
    }

    private static Field[] getDeclaredFields(TypeLiteral type) {
        return DeclaredMembers.getDeclaredFields(type.getRawType());
    }

    private static Method[] getDeclaredMethods(TypeLiteral type) {
        return DeclaredMembers.getDeclaredMethods(type.getRawType());
    }

    /**
     * Returns true if the method is eligible to be injected.  This is different than
     * {@link #isValidMethod}, because ineligibility will not drop a method
     * from being injected if a superclass was eligible & valid.
     * Bridge & synthetic methods are excluded from eligibility for two reasons:
     *
     * 

Prior to Java8, javac would generate these methods in subclasses without * annotations, which means this would accidentally stop injecting a method * annotated with {@link javax.inject.Inject}, since the spec says to stop * injecting if a subclass isn't annotated with it. * *

Starting at Java8, javac copies the annotations to the generated subclass * method, except it leaves out the generic types. If this considered it a valid * injectable method, this would eject the parent's overridden method that had the * proper generic types, and would use invalid injectable parameters as a result. * *

The fix for both is simply to ignore these synthetic bridge methods. */ private static boolean isEligibleForInjection(Method method, boolean statics) { return Modifier.isStatic(method.getModifiers()) == statics && !method.isBridge() && !method.isSynthetic(); } private static boolean isValidMethod(InjectableMethod injectableMethod, Errors errors) { boolean result = true; if (injectableMethod.jsr330) { Method method = injectableMethod.method; if (Modifier.isAbstract(method.getModifiers())) { errors.cannotInjectAbstractMethod(method); result = false; } if (method.getTypeParameters().length > 0) { errors.cannotInjectMethodWithTypeParameters(method); result = false; } } return result; } private static List> hierarchyFor(TypeLiteral type) { List> hierarchy = new ArrayList<>(); TypeLiteral current = type; while (current.getRawType() != Object.class) { hierarchy.add(current); current = current.getSupertype(current.getRawType().getSuperclass()); } return hierarchy; } /** * Returns true if a overrides b. Assumes signatures of a and b are the same and a's declaring * class is a subclass of b's declaring class. */ private static boolean overrides(Method a, Method b) { // See JLS section 8.4.8.1 int modifiers = b.getModifiers(); if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) { return true; } if (Modifier.isPrivate(modifiers)) { return false; } // b must be package-private return a.getDeclaringClass().getPackage().equals(b.getDeclaringClass().getPackage()); } private ImmutableList> forMember(Member member, TypeLiteral type, Annotation[][] paramterAnnotations) { Errors errors = new Errors(member); List> dependencies = Lists.newArrayList(); int index = 0; for (TypeLiteral parameterType : type.getParameterTypes(member)) { try { Annotation[] parameterAnnotations = paramterAnnotations[index]; Key key = Annotations.getKey(parameterType, member, parameterAnnotations, errors); dependencies.add(newDependency(key, Nullability.allowsNull(parameterAnnotations), index)); index++; } catch (ConfigurationException e) { errors.merge(e.getErrorMessages()); } catch (ErrorsException e) { errors.merge(e.getErrors()); } } errors.throwConfigurationExceptionIfErrorsExist(); return ImmutableList.copyOf(dependencies); } // This method is necessary to create a Dependency with proper generic type information private Dependency newDependency(Key key, boolean allowsNull, int parameterIndex) { return new Dependency<>(this, key, allowsNull, parameterIndex); } /** * Returns the injected constructor, field, or method. */ public Member getMember() { // TODO: Don't expose the original member (which probably has setAccessible(true)). return member; } /** * Returns the dependencies for this injection point. If the injection point is for a method or * constructor, the dependencies will correspond to that member's parameters. Field injection * points always have a single dependency for the field itself. * * @return a possibly-empty list */ public List> getDependencies() { return dependencies; } /** * Returns true if this injection point shall be skipped if the injector cannot resolve bindings * for all required dependencies. Both explicit bindings (as specified in a module), and implicit * bindings ({@literal @}{@link com.google.inject.ImplementedBy ImplementedBy}, default * constructors etc.) may be used to satisfy optional injection points. */ public boolean isOptional() { return optional; } /** * Returns true if the element is annotated with {@literal @}{@link Toolable}. */ public boolean isToolable() { return ((AnnotatedElement) member).isAnnotationPresent(Toolable.class); } /** * Returns the generic type that defines this injection point. If the member exists on a * parameterized type, the result will include more type information than the member's {@link * Member#getDeclaringClass() raw declaring class}. */ public TypeLiteral getDeclaringType() { return declaringType; } @Override public boolean equals(Object o) { return o instanceof InjectionPoint && member.equals(((InjectionPoint) o).member) && declaringType.equals(((InjectionPoint) o).declaringType); } @Override public int hashCode() { return member.hashCode() ^ declaringType.hashCode(); } @Override public String toString() { return Classes.toString(member); } /** * Position in type hierarchy. */ enum Position { TOP, // No need to check for overridden methods MIDDLE, BOTTOM // Methods won't be overridden } /** * Node in the doubly-linked list of injectable members (fields and methods). */ static abstract class InjectableMember { final TypeLiteral declaringType; final boolean optional; final boolean jsr330; InjectableMember previous; InjectableMember next; InjectableMember(TypeLiteral declaringType, Annotation atInject) { this.declaringType = declaringType; if (atInject.annotationType() == javax.inject.Inject.class) { optional = false; jsr330 = true; return; } jsr330 = false; optional = ((Inject) atInject).optional(); } abstract InjectionPoint toInjectionPoint(); } static class InjectableField extends InjectableMember { final Field field; InjectableField(TypeLiteral declaringType, Field field, Annotation atInject) { super(declaringType, atInject); this.field = field; } @Override InjectionPoint toInjectionPoint() { return new InjectionPoint(declaringType, field, optional); } } static class InjectableMethod extends InjectableMember { final Method method; /** * true if this method overrode a method that was annotated * with com.google.inject.Inject. used to allow different * override behavior for guice inject vs javax.inject.Inject */ boolean overrodeGuiceInject; InjectableMethod(TypeLiteral declaringType, Method method, Annotation atInject) { super(declaringType, atInject); this.method = method; } @Override InjectionPoint toInjectionPoint() { return new InjectionPoint(declaringType, method, optional); } public boolean isFinal() { return Modifier.isFinal(method.getModifiers()); } } /** * Linked list of injectable members. */ static class InjectableMembers { InjectableMember head; InjectableMember tail; void add(InjectableMember member) { if (head == null) { head = tail = member; } else { member.previous = tail; tail.next = member; tail = member; } } void remove(InjectableMember member) { if (member.previous != null) { member.previous.next = member.next; } if (member.next != null) { member.next.previous = member.previous; } if (head == member) { head = member.next; } if (tail == member) { tail = member.previous; } } boolean isEmpty() { return head == null; } } /** * Keeps track of injectable methods so we can remove methods that get overridden in O(1) time. * Uses our position in the type hierarchy to perform optimizations. */ static class OverrideIndex { final InjectableMembers injectableMembers; Map> bySignature; Position position = Position.TOP; /* Caches the signature for the last method. */ Method lastMethod; Signature lastSignature; OverrideIndex(InjectableMembers injectableMembers) { this.injectableMembers = injectableMembers; } /** * Removes a method overridden by the given method, if present. In order to * remain backwards compatible with prior Guice versions, this will *not* * remove overridden methods if 'alwaysRemove' is false and the overridden * signature was annotated with a com.google.inject.Inject. * * @param method The method used to determine what is overridden and should be * removed. * @param alwaysRemove true if overridden methods should be removed even if they were * guice @Inject * @param injectableMethod if this method overrode any guice @Inject methods, * {@link InjectableMethod#overrodeGuiceInject} is set to true */ boolean removeIfOverriddenBy(Method method, boolean alwaysRemove, InjectableMethod injectableMethod) { if (position == Position.TOP) { // If we're at the top of the hierarchy, there's nothing to override. return false; } if (bySignature == null) { // We encountered a method in a subclass. Time to index the // methods in the parent class. bySignature = new HashMap<>(); for (InjectableMember member = injectableMembers.head; member != null; member = member.next) { if (!(member instanceof InjectableMethod)) { continue; } InjectableMethod im = (InjectableMethod) member; if (im.isFinal()) { continue; } List methods = new ArrayList<>(); methods.add(im); bySignature.put(new Signature(im.method), methods); } } lastMethod = method; Signature signature = lastSignature = new Signature(method); List methods = bySignature.get(signature); boolean removed = false; if (methods != null) { for (Iterator iterator = methods.iterator(); iterator.hasNext(); ) { InjectableMethod possiblyOverridden = iterator.next(); if (overrides(method, possiblyOverridden.method)) { boolean wasGuiceInject = !possiblyOverridden.jsr330 || possiblyOverridden.overrodeGuiceInject; if (injectableMethod != null) { injectableMethod.overrodeGuiceInject = wasGuiceInject; } // Only actually remove the methods if we want to force // remove or if the signature never specified @com.google.inject.Inject // somewhere. if (alwaysRemove || !wasGuiceInject) { removed = true; iterator.remove(); injectableMembers.remove(possiblyOverridden); } } } } return removed; } /** * Adds the given method to the list of injection points. Keeps track of it in this index * in case it gets overridden. */ void add(InjectableMethod injectableMethod) { injectableMembers.add(injectableMethod); if (position == Position.BOTTOM || injectableMethod.isFinal()) { // This method can't be overridden, so there's no need to index it. return; } if (bySignature != null) { // Try to reuse the signature we created during removal Signature signature = injectableMethod.method == lastMethod ? lastSignature : new Signature(injectableMethod.method); List methods = bySignature.computeIfAbsent(signature, k -> new ArrayList<>()); methods.add(injectableMethod); } } } /** * A method signature. Used to handle method overridding. */ static class Signature { final String name; final Class[] parameterTypes; final int hash; Signature(Method method) { this.name = method.getName(); this.parameterTypes = method.getParameterTypes(); int h = name.hashCode(); h = h * 31 + parameterTypes.length; for (Class parameterType : parameterTypes) { h = h * 31 + parameterType.hashCode(); } this.hash = h; } @Override public int hashCode() { return this.hash; } @Override public boolean equals(Object o) { if (!(o instanceof Signature)) { return false; } Signature other = (Signature) o; if (!name.equals(other.name)) { return false; } if (parameterTypes.length != other.parameterTypes.length) { return false; } for (int i = 0; i < parameterTypes.length; i++) { if (parameterTypes[i] != other.parameterTypes[i]) { return false; } } return true; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy