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

org.commonvox.collections.KeyComponentProfile Maven / Gradle / Ivy

Go to download

An OrderedSet extends the standard Java collections framework to provide composite-key based ordering of a set of values (analogous to composite key ordering in a relational database).

The newest version!
/*
 * Copyright (C) 2016 Daniel Vimont
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.commonvox.collections;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * An array of KeyComponentProfile objects specifies the composite-key
 * structure which orders the elements in an {@link OrderedSet}.
 * The construction of a KeyComponentProfile establishes how its
 * corresponding keyComponentClass-objects are to be automatically retrieved
 * and ordered when valueClass-elements are added to an {@link OrderedSet}
 * to which the KeyComponentProfile belongs.
 * A single KeyComponentProfile object may be used in multiple
 * {@link OrderedSet}s.
 * 

* Usage examples can be found * here. * * @author Daniel Vimont * @param The valueClass, i.e. the class of elements contained in * the {@link OrderedSet} to which the KeyComponentProfile * belongs. * The valueClass must match the {@literal } type-parameter * of any {@link OrderedSet} to which the KeyComponentProfile * belongs (enforced at compile-time). */ public class KeyComponentProfile implements Serializable { private final Class valueClass; private final Class keyComponentClass; private final List valueClassMethodsThatReturnKeyComponents; private final KeyComponentBasis indexComponentBasis; private final String indexComponentName; private final Comparator keyComponentClassComparator; private final int immutableHashCode; static final Method IDENTITY_METHOD; static final String INVALID_METHOD_MESSAGE_OPENER = "Invalid method(s) submitted in the KeyComponentProfile's " + "methodsThatReturnKeyComponents array: "; static enum KeyComponentBasis { CLASS, METHOD, IDENTITY // , FIELD }; static { Method dummyMethod; try { dummyMethod = GetThisObject.class.getMethod("getThis"); } catch (NoSuchMethodException e) { throw new InternalError("Internal coding error has resulted in failure " + "to set IDENTITY_METHOD constant in <" + KeyComponentProfile.class.getSimpleName() + "> class."); } IDENTITY_METHOD = dummyMethod; } /** * THIS PACKAGE-PRIVATE CONSTRUCTOR USED ONLY INTERNALLY BY MapNode class! * Constructs an IDENTITY KeyComponentProfile for the * {@link OrderedSet} to which it belongs; * the ordering maintained by such a KeyComponentProfile is based * upon the valueClass itself (as stipulated by the * {@literal } type-parameter). */ KeyComponentProfile() { this.indexComponentBasis = KeyComponentBasis.IDENTITY; this.valueClass = null; this.indexComponentName = KeyComponentBasis.IDENTITY.toString(); this.keyComponentClass = Object.class; this.valueClassMethodsThatReturnKeyComponents = new ArrayList(); this.valueClassMethodsThatReturnKeyComponents.add(IDENTITY_METHOD); this.keyComponentClassComparator = null; this.immutableHashCode = computeImmutableHashCode(); } /** * Constructs a KeyComponentProfile belonging to an * {@link OrderedSet} containing elements of * class valueClass; * the ordering maintained by a KeyComponentProfile is stipulated * by the keyComponentClass parameter, which must either be the same * as the valueClass or must correspond to a class of * objects returned by one or more of the valueClass's parameterless * "get" methods; submission of methodsReturningKeyComponents vararg * parameters is optional. *

* Usage examples can be found * here. *

* CONSTRUCTION *
* During construction of the KeyComponentProfile, automatic * assembly of an internal valueClassMethodsThatReturnKeyComponents * array is done via Java reflection. Any non-private, non-static, * parameterless method of the valueClass which returns either a * single instance of a keyComponentClass-object or a Collection of * keyComponentClass-objects is added to the * valueClassMethodsThatReturnKeyComponents array. * A subset of these methods may optionally be stipulated by the programmer * via the optional methodsReturningKeyComponents vararg parameters; * if one or more of these vararg parameters are submitted, the * valueClassMethodsThatReturnKeyComponents array is limited to this * subset of the valueClass "get" methods. *

* USAGE IN AN {@link OrderedSet} *
* Subsequent to KeyComponentProfile construction, as * valueClass-elements are * {@link OrderedSet#add(java.lang.Object) add}ed * to an {@link OrderedSet} * to which the KeyComponentProfile belongs, * keyComponentClass-objects are automatically retrieved and ordered. * If the keyComponentClass equals the valueClass, then the * valueClass-element itself serves the role of * keyComponentClass-object. Otherwise, * keyComponentClass-objects are retrieved via reflection-based * invocation of the "get" methods in the * valueClassMethodsThatReturnKeyComponents array. * All retrieved keyComponentClass-objects are internally ordered via backing * * TreeMaps, either in * * natural order or * * hashCode order. * If the keyComponentClass implements the * Comparable interface, its objects are ordered * in natural order; otherwise they are ordered in hashCode order. * * @param keyComponentClass * @param valueClass The class of elements contained in the * {@link OrderedSet} to which the KeyComponentProfile * belongs. * This value must match that of the {@literal } type-parameter * (enforced at compile-time). * @param keyComponentClass The class of the objects to be retrieved and * ordered by the KeyComponentProfile. * The keyComponentClass must either be the same * as the valueClass or must correspond to a class of * objects returned by one or more of the valueClass's parameterless * "get" methods. * @param methodsReturningKeyComponents an optional subset of the * valueClass's parameterless "get" methods; if submitted, keyComponent * reflection-based retrieval (as described above) will be limited to * invocation of this subset of methods. */ public KeyComponentProfile(Class valueClass, Class keyComponentClass, Method... methodsReturningKeyComponents) { this(valueClass, keyComponentClass, null, methodsReturningKeyComponents); } /** * Constructs a KeyComponentProfile belonging to an * {@link OrderedSet} containing elements of * class valueClass; * the ordering maintained by a KeyComponentProfile is stipulated * by the keyComponentClass parameter, which must either be the same * as the valueClass or must correspond to a class of * objects returned by one or more of the valueClass's parameterless * "get" methods; submission of methodsReturningKeyComponents vararg * parameters is optional. *

* Usage examples can be found * here. *

* CONSTRUCTION *
* During construction of the KeyComponentProfile, automatic * assembly of an internal valueClassMethodsThatReturnKeyComponents * array is done via Java reflection. Any non-private, non-static, * parameterless method of the valueClass which returns either a * single instance of a keyComponentClass-object or a Collection of * keyComponentClass-objects is added to the * valueClassMethodsThatReturnKeyComponents array. * A subset of these methods may optionally be stipulated by the programmer * via the optional methodsReturningKeyComponents vararg parameters; * if one or more of these vararg parameters are submitted, the * valueClassMethodsThatReturnKeyComponents array is limited to this * subset of the valueClass "get" methods. *

* USAGE IN AN {@link OrderedSet} *
* Subsequent to KeyComponentProfile construction, as * valueClass-elements are * {@link OrderedSet#add(java.lang.Object) add}ed * to an {@link OrderedSet} * to which the KeyComponentProfile belongs, * keyComponentClass-objects are automatically retrieved and ordered. * If the keyComponentClass equals the valueClass, then the * valueClass-element itself serves the role of * keyComponentClass-object. Otherwise, * keyComponentClass-objects are retrieved via reflection-based * invocation of the "get" methods in the * valueClassMethodsThatReturnKeyComponents array. * The retrieved keyComponentClass-objects are then ordered using a * TreeMap structure, placing the * keyComponentClass-objects in the order maintained by the * Comparator stipulated in the keyComponentClassComparator parameter. * * @param keyComponentClass * @param valueClass The class of elements contained in the * {@link OrderedSet} to which the KeyComponentProfile * belongs. * This value must match that of the {@literal } type-parameter * (enforced at compile-time). * @param keyComponentClass The class of the objects to be retrieved and * ordered by the KeyComponentProfile. * The keyComponentClass must either be the same * as the valueClass or must correspond to a class of * objects returned by one or more of the valueClass's parameterless * "get" methods. * @param keyComponentClassComparator Comparator to be used for ordering * retrieved keyComponentClass-objects. * @param methodsReturningKeyComponents an optional subset of the * valueClass's parameterless "get" methods; if submitted, keyComponent * reflection-based retrieval (as described above) will be limited to * invocation of this subset of methods. */ public KeyComponentProfile(Class valueClass, Class keyComponentClass, Comparator keyComponentClassComparator, Method... methodsReturningKeyComponents) { this.valueClass = valueClass; this.keyComponentClass = keyComponentClass; this.keyComponentClassComparator = keyComponentClassComparator; StringBuilder nameBuilder = new StringBuilder(); nameBuilder.append(this.keyComponentClass.getSimpleName()); if (this.valueClass.equals(this.keyComponentClass)) { this.indexComponentBasis = KeyComponentBasis.IDENTITY; this.valueClassMethodsThatReturnKeyComponents = new ArrayList(); this.valueClassMethodsThatReturnKeyComponents.add(IDENTITY_METHOD); } else { if (methodsReturningKeyComponents == null || methodsReturningKeyComponents.length == 0) { this.indexComponentBasis = KeyComponentBasis.CLASS; this.valueClassMethodsThatReturnKeyComponents = getMethodsThatReturnObjectsOfKeyComponentClass(); if (this.valueClassMethodsThatReturnKeyComponents.isEmpty()) { throw new IllegalArgumentException("Invalid keyComponentClass, <" + this.keyComponentClass.getSimpleName() + ">, submitted for " + "construction of KeyComponentProfile. No parameterless " + "method returning object of this keyComponentClass " + "(or Collection of objects of this keyComponentClass) " + "is declared by class (or superclass of) valueClass, <" + this.valueClass.getSimpleName() + ">"); } } else { this.indexComponentBasis = KeyComponentBasis.METHOD; this.valueClassMethodsThatReturnKeyComponents = Arrays.asList(methodsReturningKeyComponents); if (this.keyComponentClass != getClassOfObjectsReturnedByMethods()) { throw new IllegalArgumentException(INVALID_METHOD_MESSAGE_OPENER + "method(s) return object(s) of class <" + getClassOfObjectsReturnedByMethods().getSimpleName() + "> which differs from submitted keyComponentClass <" + this.keyComponentClass.getSimpleName() + ">"); } } for (Method method : this.valueClassMethodsThatReturnKeyComponents) { nameBuilder.append(";").append(method.getName()); } } this.indexComponentName = nameBuilder.toString(); this.immutableHashCode = computeImmutableHashCode(); } private List getMethodsThatReturnObjectsOfKeyComponentClass() { List methodSet = new ArrayList(); // examine all declared and inherited non-static PUBLIC methods for (Method method : this.valueClass.getMethods()) { if (Modifier.isStatic(method.getModifiers())) { continue; } if (methodIsParameterlessAndReturnsObjectsOfKeyComponentClass(method)) { methodSet.add(method); } } // examine all declared non-static PROTECTED and PACKAGE-PRIVATE methods for (Method method : this.valueClass.getDeclaredMethods()) { int modifiers = method.getModifiers(); if (Modifier.isStatic(modifiers) || Modifier.isPublic(modifiers) || Modifier.isPrivate(modifiers)) { continue; } if (methodIsParameterlessAndReturnsObjectsOfKeyComponentClass(method)) { method.setAccessible(true); methodSet.add(method); } } return methodSet; } private boolean methodIsParameterlessAndReturnsObjectsOfKeyComponentClass(Method method) { if (method.getParameterCount() > 0) { return false; } if (method.getReturnType().equals(this.keyComponentClass)) { return true; } else if (Collection.class.isAssignableFrom(method.getReturnType())) { ParameterizedType collectionType = (ParameterizedType) method.getGenericReturnType(); Type typeOfObjectsInCollection = collectionType.getActualTypeArguments()[0]; if (typeOfObjectsInCollection.getTypeName().equals( this.keyComponentClass.getTypeName())) { return true; } } return false; } private Class getClassOfObjectsReturnedByMethods() { Class returnedClass = null; for (Method method : this.valueClassMethodsThatReturnKeyComponents) { if (method == null) { throw new IllegalArgumentException(INVALID_METHOD_MESSAGE_OPENER + "."); } if (!method.getDeclaringClass().isAssignableFrom(this.valueClass)) { throw new IllegalArgumentException(INVALID_METHOD_MESSAGE_OPENER + "<" + method.getName() + "> method's declaring class is <" + method.getDeclaringClass().getSimpleName() + ">, which is not assignable from valueClass, <" + this.valueClass.getSimpleName() + ">."); } if (method.getParameterCount() > 0) { throw new IllegalArgumentException(INVALID_METHOD_MESSAGE_OPENER + "<" + method.getName() + "> method is not parameterless as required."); } if (Collection.class.isAssignableFrom(method.getReturnType())) { ParameterizedType collectionType = (ParameterizedType) method.getGenericReturnType(); Type typeOfObjectsInCollection = collectionType.getActualTypeArguments()[0]; try { Class classInCollection = Class.forName(typeOfObjectsInCollection.getTypeName()); if (returnedClass == null) { returnedClass = classInCollection; } else if (!returnedClass.equals(classInCollection)) { throw new IllegalArgumentException(INVALID_METHOD_MESSAGE_OPENER + "<" + method.getName() + "> method returns Collection of objects of class " + "which differs from class of object returned by " + "other method(s) in the KeyComponentProfile's " + " array."); } } catch (ClassNotFoundException e) { throw new IllegalArgumentException(INVALID_METHOD_MESSAGE_OPENER + "<" + method.getName() + "> method returns Collection of objects " + "belonging to a Class that cannot be found.", e); } } else { if (returnedClass == null) { returnedClass = method.getReturnType(); } else if (!returnedClass.equals(method.getReturnType())) { throw new IllegalArgumentException(INVALID_METHOD_MESSAGE_OPENER + "<" + method.getName() + "> method returns object of class which differs from " + "class of object returned by other method(s) in the " + "KeyComponentProfile's " + " array."); } } } return returnedClass; } KeyComponentBasis getKeyComponentBasis() { return this.indexComponentBasis; } Class getKeyComponentClass() { return this.keyComponentClass; } Comparator getKeyComponentClassComparator() { return this.keyComponentClassComparator; } List getKeyComponentGetMethods() { return this.valueClassMethodsThatReturnKeyComponents; } private int computeImmutableHashCode() { int hash = 5; hash = 13 * hash + (this.valueClass != null ? this.valueClass.hashCode() : 0); hash = 13 * hash + (this.keyComponentClass != null ? this.keyComponentClass.hashCode() : 0); hash = 13 * hash + (this.valueClassMethodsThatReturnKeyComponents != null ? this.valueClassMethodsThatReturnKeyComponents.hashCode() : 0); hash = 13 * hash + (this.indexComponentBasis != null ? this.indexComponentBasis.hashCode() : 0); hash = 13 * hash + (this.keyComponentClassComparator != null ? this.keyComponentClassComparator.hashCode() : 0); return hash; } @Override public int hashCode() { return immutableHashCode; } @Override public boolean equals(Object other) { if (other == null) { return false; } if (this.getClass() != other.getClass()) { return false; } return this.immutableHashCode == ((KeyComponentProfile)other).immutableHashCode; } // public int compareTo(KeyComponentProfile o) { // return this.immutableHashCode - o.immutableHashCode; // } /** * Returns a String representation of this object. The String representation * consists of the KeyComponentProfile's basis (either CLASS, METHOD, or * IDENTITY) and the name of its basis entity. * @return a String representation of this object */ @Override public String toString() { return ""; } Set getKeyComponentSet(V masterObject) { Set keyComponentSet = new HashSet(); for (Method currentGetMethod : getKeyComponentGetMethods()) { if (Collection.class.isAssignableFrom(currentGetMethod.getReturnType())) { try { @SuppressWarnings("unchecked") Collection retrievedKeyCollection = (Collection) currentGetMethod.invoke(masterObject); if (retrievedKeyCollection != null) { keyComponentSet.addAll(retrievedKeyCollection); } } catch (InvocationTargetException e) { throwMapNodePopulationInternalError(e); } catch (IllegalAccessException e) { throwMapNodePopulationInternalError(e); } } else { Object retrievedKey = null; if (getKeyComponentBasis(). equals(KeyComponentProfile.KeyComponentBasis.IDENTITY)) { retrievedKey = masterObject; } else { try { retrievedKey = currentGetMethod.invoke(masterObject); } catch (InvocationTargetException e) { throwMapNodePopulationInternalError(e); } catch (IllegalAccessException e) { throwMapNodePopulationInternalError(e); } } if (retrievedKey != null) { keyComponentSet.add(retrievedKey); } } } return keyComponentSet; } private void throwMapNodePopulationInternalError(Exception e) { throw new InternalError("Unanticipated " + e.getClass().getSimpleName() + " encountered while populating internal " + MapNode.class.getSimpleName() + ".", e); } /** * This interface used for IDENTITY_METHOD */ private interface GetThisObject { public void getThis(); } }