View Javadoc
1   package org.pojomatic.internal;
2   
3   import java.lang.reflect.Method;
4   import java.lang.reflect.Modifier;
5   import java.util.*;
6   
7   /**
8    * A mutable set of methods which can be overridden.  All methods are assumed to take no arguments
9    * and either public, protected or package private.
10   */
11  class OverridableMethods {
12  
13    /**
14     * Check to see if roles should be added to a method, and add them if so.  Only roles not already
15     * on the method will be added.  If {@code method} already has an
16     * {@link PropertyRole#EQUALS EQUALS} role, and it is requested to add the
17     * {@link PropertyRole#HASH_CODE HASH_CODE} role, an {@link IllegalArgumentException} will be
18     * thrown.
19     *
20     * @param method the method to check
21     * @param newRoles the roles to add
22     * @return the roles which were actually added.
23     * @throws IllegalArgumentException if {@code method} already has an
24     * {@link PropertyRole#EQUALS EQUALS} role, and it is requested to add the
25     * {@link PropertyRole#HASH_CODE HASH_CODE} role
26     */
27    Set<PropertyRole> checkAndMaybeAddRolesToMethod(Method method, Set<PropertyRole> newRoles) {
28      Set<PropertyRole> existingRoles = findExistingRoles(method);
29      if (existingRoles.contains(PropertyRole.EQUALS)
30        && !existingRoles.contains(PropertyRole.HASH_CODE)
31        && newRoles.contains(PropertyRole.HASH_CODE)) {
32        throw new IllegalArgumentException(
33          "Method " + method.getDeclaringClass().getName() + "." + method.getName()
34            + " is requested to be included in hashCode computations, but already overrides a method"
35            + " which is requested for equals computations, but not hashCode computations.");
36      }
37      Set<PropertyRole> addedRoles = EnumSet.noneOf(PropertyRole.class);
38      for (PropertyRole role : newRoles) {
39        if (!existingRoles.contains(role)) {
40          addedRoles.add(role);
41          existingRoles.add(role);
42        }
43      }
44      return addedRoles;
45    }
46  
47    private Set<PropertyRole> findExistingRoles(Method method) {
48      Set<PropertyRole> existingRoles;
49      if (isPackagePrivate(method)) {
50        // This can only override another package private method
51        PackageMethod key = new PackageMethod(method);
52        existingRoles = packageMethods.get(key);
53        if (existingRoles == null) {
54          existingRoles = EnumSet.noneOf(PropertyRole.class);
55          packageMethods.put(key, existingRoles);
56        }
57      }
58      else {
59        // If there is a public method already declared, then this is an override.  Otherwise,
60        // we need to track it as a public override going forward, even if it is overriding a
61        // superclass method which was declared package private.
62        existingRoles = publicOrProtectedMethods.get(method.getName());
63        if (existingRoles == null) {
64          existingRoles = packageMethods.get(new PackageMethod(method));
65        }
66        if (existingRoles == null) {
67          existingRoles = EnumSet.noneOf(PropertyRole.class);
68          publicOrProtectedMethods.put(method.getName(), existingRoles);
69        }
70      }
71      return existingRoles;
72    }
73  
74    /**
75     * A bean to track the package and name of a package-private method
76     */
77    private static class PackageMethod {
78      PackageMethod(Method method) {
79        name = method.getName();
80        pakage = method.getDeclaringClass().getPackage();
81      }
82  
83      final String name;
84      final Package pakage;
85  
86      @Override
87      public int hashCode() {
88        return name.hashCode() * 31 + pakage.hashCode();
89      }
90  
91      @Override
92      public boolean equals(Object obj) {
93        if (this == obj) {
94          return true;
95        }
96        if (obj instanceof PackageMethod) {
97          PackageMethod other = (PackageMethod) obj;
98          return name.equals(other.name) && pakage.equals(other.pakage);
99        }
100       else {
101         return false;
102       }
103     }
104   }
105 
106   private final Map<String, Set<PropertyRole>> publicOrProtectedMethods = new HashMap<>();
107   private final Map<PackageMethod, Set<PropertyRole>> packageMethods = new HashMap<>();
108 
109 
110   private static boolean isPackagePrivate(Method method) {
111     return !(Modifier.isPublic(method.getModifiers())
112       || Modifier.isProtected(method.getModifiers()));
113   }
114 
115 }