OverridableMethods.java
package org.pojomatic.internal;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* A mutable set of methods which can be overridden. All methods are assumed to take no arguments
* and either public, protected or package private.
*/
class OverridableMethods {
/**
* Check to see if roles should be added to a method, and add them if so. Only roles not already
* on the method will be added. If {@code method} already has an
* {@link PropertyRole#EQUALS EQUALS} role, and it is requested to add the
* {@link PropertyRole#HASH_CODE HASH_CODE} role, an {@link IllegalArgumentException} will be
* thrown.
*
* @param method the method to check
* @param newRoles the roles to add
* @return the roles which were actually added.
* @throws IllegalArgumentException if {@code method} already has an
* {@link PropertyRole#EQUALS EQUALS} role, and it is requested to add the
* {@link PropertyRole#HASH_CODE HASH_CODE} role
*/
Set<PropertyRole> checkAndMaybeAddRolesToMethod(Method method, Set<PropertyRole> newRoles) {
Set<PropertyRole> existingRoles = findExistingRoles(method);
if (existingRoles.contains(PropertyRole.EQUALS)
&& !existingRoles.contains(PropertyRole.HASH_CODE)
&& newRoles.contains(PropertyRole.HASH_CODE)) {
throw new IllegalArgumentException(
"Method " + method.getDeclaringClass().getName() + "." + method.getName()
+ " is requested to be included in hashCode computations, but already overrides a method"
+ " which is requested for equals computations, but not hashCode computations.");
}
Set<PropertyRole> addedRoles = EnumSet.noneOf(PropertyRole.class);
for (PropertyRole role : newRoles) {
if (!existingRoles.contains(role)) {
addedRoles.add(role);
existingRoles.add(role);
}
}
return addedRoles;
}
private Set<PropertyRole> findExistingRoles(Method method) {
Set<PropertyRole> existingRoles;
if (isPackagePrivate(method)) {
// This can only override another package private method
PackageMethod key = new PackageMethod(method);
existingRoles = packageMethods.get(key);
if (existingRoles == null) {
existingRoles = EnumSet.noneOf(PropertyRole.class);
packageMethods.put(key, existingRoles);
}
}
else {
// If there is a public method already declared, then this is an override. Otherwise,
// we need to track it as a public override going forward, even if it is overriding a
// superclass method which was declared package private.
existingRoles = publicOrProtectedMethods.get(method.getName());
if (existingRoles == null) {
existingRoles = packageMethods.get(new PackageMethod(method));
}
if (existingRoles == null) {
existingRoles = EnumSet.noneOf(PropertyRole.class);
publicOrProtectedMethods.put(method.getName(), existingRoles);
}
}
return existingRoles;
}
/**
* A bean to track the package and name of a package-private method
*/
private static class PackageMethod {
PackageMethod(Method method) {
name = method.getName();
pakage = method.getDeclaringClass().getPackage();
}
final String name;
final Package pakage;
@Override
public int hashCode() {
return name.hashCode() * 31 + pakage.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof PackageMethod) {
PackageMethod other = (PackageMethod) obj;
return name.equals(other.name) && pakage.equals(other.pakage);
}
else {
return false;
}
}
}
private final Map<String, Set<PropertyRole>> publicOrProtectedMethods = new HashMap<>();
private final Map<PackageMethod, Set<PropertyRole>> packageMethods = new HashMap<>();
private static boolean isPackagePrivate(Method method) {
return !(Modifier.isPublic(method.getModifiers())
|| Modifier.isProtected(method.getModifiers()));
}
}