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

org.flexdock.util.ClassMapping Maven / Gradle / Ivy

The newest version!
/*
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.flexdock.util;

import java.util.WeakHashMap;

/**
 * This class manages associations between classes and object instances. It
 * allows for mappings between a class and its subclasses and another associated
 * class, or an associated instance of a class.
 * 

* This class is useful for "handler" type logic in which a handler class must * be mapped to the classes it is designed to handle. Consider the class * hierarchy of {@code Foo}, {@code Bar}, and {@code Baz}, where {@code Bar} * extends {@code Foo} and {@code Baz} extends {@code Bar}. * *

 * Foo.class
 *   |-Bar.class
 *       |-Baz.class
 * 
* * Each of these classes is ultimately a type of {@code Foo}. Some operation is * performed on instances of {@code Foo} and a set of handler classes are used * to handle different types of {@code Foo}. Adding a mapping between * {@code Foo.class} and {@code Handler1.class} will create an association * between {@code Foo} and all strict, non-specific subclasses of * {@code Foo} and {@code Handler1.class}. *

* This means that given any instance of {@code Foo}, calling * {@code getClassMapping(Object obj)} will return {@code Handler1.class} as the * class responsible for handling the {@code Foo} instance. This includes * {@code Bar} and {@code Baz}. All types of {@code Foo} now have a implicit * association with {@code Handler1.class} *

* However, if this method is subsequently called with arguments of * {@code Baz.class} and {@code Handler2.class}, then a specific * subclass mapping has been introduced for {@code Baz}. Associations apply to * the given class and non-specific subclasses. Thus, the * {@code Handler1.class} association remains for {@code Foo} and {@code Bar}, * but no longer for {@code Baz}. Calling {@code getClassMapping(Object obj)} * with an instance of {@code Baz} will now return {@code Handler2.class}. * *

 *  Foo.class ---------------> (maps to Handler1.class)
 *    |-Bar.class -----------> (maps to Handler1.class)
 *        |-Baz.class -------> (maps to Handler2.class)
 * 
* * Polymorphic identity within the class association uses strict * subclasses. This means that the {@code Handler1.class} mapping for * {@code Foo}, {@code Bar}, and all non-specific subclasses will hold true. * However, if {@code Foo} happens to implement the interface {@code Qwerty}, * the class mapping relationship will not hold true for all implementations of * {@code Qwerty}. Only subclasses of {@code Foo}. * *
 *  Foo.class (implements Qwerty) ----------------> (maps to Handler1.class)
 *    |-Bar.class (implements Qwerty) ------------> (maps to Handler1.class)
 *        |-Baz.class (implements Qwerty) --------> (maps to Handler2.class)
 *  Asdf.class (implements Qwerty) ---------------> (maps to nothing)
 * 
* * @author Christopher Butler */ public class ClassMapping { private WeakHashMap classes; private WeakHashMap instances; private Class defaultClass; private Object defaultInstance; /** * Creates a new {@code ClassMapping} instance with the specified default * values. All calls to {@code getClassMapping(Class key)} for this * {@code ClassMapping} in which a specific mapping cannot be found will * return the specified {@code defaultClass}. All calls to * {@code getClassInstance(Class key)} in which a specific mapping cannot be * found will return the specified {@code defaultInstance}. * * @param defaultClass * the default class used by this {@code ClassMapping} * @param defaultInstance * the default object instance used by this {@code ClassMapping} */ public ClassMapping(Class defaultClass, Object defaultInstance) { this.defaultClass = defaultClass; this.defaultInstance = defaultInstance; classes = new WeakHashMap(4); instances = new WeakHashMap(4); } /** * Adds a mapping between the {@code Class} type of the specified * {@code Object} and the specified {@code value}. This method calls * {@code getClass()} on the specified {@code Object} and dispatches to * {@code addClassMapping(Class key, Class value)}. If either {@code obj} * or {@code value} are {@code null}, then this method returns with no * action taken. The {@code value} class may later be retrieved by calling * {@code getClassMapping(Class key)} using the specified {@code key} class ({@code obj.getClass()}) * or any subclass thereof for which a specific class mapping does not * already exist. * * @param obj * the {@code Object} whose {@code Class} will be mapped to the * specified {@code value}. * @param value * the {@code Class} to be associated with the specified key * @see #addClassMapping(Object, Class) * @see #getClassMapping(Object) * @see #getClassMapping(Class) * @see #removeClassMapping(Object) * @see #removeClassMapping(Class) */ public void addClassMapping(Object obj, Class value) { Class key = obj == null ? null : obj.getClass(); addClassMapping(key, value); } /** * Adds a mapping between the key {@code Class} and the specified * {@code value}. If either {@code key} or {@code value} are {@code null}, * then this method returns with no action taken. This method creates an * association between the specified {@code key} {@code Class} and all * strict, non-specific subclasses and the specified {@code value} * {@code Class}. The {@code value} class may later be retrieved by calling * getClassMapping(Class key) using the specified {@code key} class or any * subclass thereof for which a specific class mapping does not already * exist. * * @param key * the {@code Class} to be mapped to the specified {@code value}. * @param value * the {@code Class} to be associated with the specified key * @see #addClassMapping(Class, Class, Object) * @see #getClassMapping(Class) * @see #removeClassMapping(Class) */ public void addClassMapping(Class key, Class value) { addClassMapping(key, value, null); } /** * Adds a mapping between the key {@code Class} and both the specified * {@code value} and specified object instance.. If either {@code key} or * {@code value} are {@code null}, then this method returns with no action * taken. This method creates an association between the specified * {@code key} {@code Class} and all strict, non-specific subclasses and the * specified {@code value} {@code Class}. The {@code value} class may later * be retrieved by calling {@code getClassMapping(Class key)} using the * specified {@code key} class or any subclass thereof for which a specific * class mapping does not already exist. *

* This method also creates an optional mapping between the {@code key} and * a particular object instance, defined by the {@code instance} parameter. * If {@code instance} is non-{@code null}, then a mapping is defined * between {@code key} and all strict, non-specific subclasses and the * object instance itself. The {@code instance} object may later be * retrieved by calling {@code getClassInstance(Class key)} using the * specified {@code key} class or any subclass thereof for which a specific * instance mapping does not already exist. If {@code instance} is * {@code null}, then no instance mapping is created. * * @param key * the {@code Class} to be mapped to the specified {@code value}. * @param value * the {@code Class} to be associated with the specified key * @param instance * the object instance to be associated with the specified key * @see #getClassMapping(Class) * @see #getClassInstance(Class) * @see #removeClassMapping(Class) */ public void addClassMapping(Class key, Class value, Object instance) { if (key == null || value == null) { return; } synchronized (classes) { classes.put(key, value); } if (instance != null) { synchronized (instances) { instances.put(key, instance); } } } /** * Removes any existing class mappings for the {@code Class} type of the * specified {@code Object}. This method calls {@code getClass()} on the * specified {@code Object} and dispatches to * {@code removeClassMapping(Class key)}. If {@code obj} is {@code null}, * then this method returns {@code null}. *

* Removing the mapping for the specified {@code Class} will also remove it * for all non-specific subclasses. This means that subclasses of the * specified {@code Class} will require specific mappings if the it is * desired for the existing mapping behavior for these classes to remain the * same. *

* If any instance mappings exist for the specified {@code Class}, they are * also removed. This means non-specific subclass instance mappings will * also be removed. * * @param obj * the {@code Object} whose {@code Class} will be removed from * the internal mapping * @return the {@code Class} whose mapping has been removed * @see #removeClassMapping(Class) * @see #addClassMapping(Object, Class) * @see #getClassMapping(Object) * @see #getClassInstance(Class) */ public Class removeClassMapping(Object obj) { Class key = obj == null ? null : obj.getClass(); return removeClassMapping(key); } /** * Removes any existing class mappings for the {@code Class} type of the * specified {@code Object}. This method calls {@code getClass()} on the * specified {@code Object} and dispatches to * {@code removeClassMapping(Class key)}. If {@code obj} is {@code null}, * then this method returns {@code null}. *

* Removing the mapping for the specified {@code Class} will also remove it * for all non-specific subclasses. This means that subclasses of the * specified {@code Class} will require specific mappings if the it is * desired for the existing mapping behavior for these classes to remain the * same. *

* If any instance mappings exist for the specified {@code Class}, they are * also removed. This means non-specific subclass instance mappings will * also be removed. * * @param key * the {@code Class} whose internal mapping will be removed * @return the {@code Class} whose mapping has been removed * @see #addClassMapping(Class, Class) * @see #addClassMapping(Class, Class, Object) * @see #getClassMapping(Object) * @see #getClassInstance(Class) */ public Class removeClassMapping(Class key) { if (key == null) { return null; } Class c = null; synchronized (classes) { c = (Class) classes.remove(key); } synchronized (instances) { instances.remove(key); } return c; } /** * Returns the {@code Class} associated with the {@code Class} of the * specified {@code Object}. If {@code obj} is {@code null}, this method * will return the value retrieved from {@code getDefaultMapping()}. * Otherwise, this method calls {@code obj.getClass()} and dispatches to * {@code getClassMapping(Class key)}. *

* If no mapping has been defined for the specified {@code Class}, then * it's superclass is checked, and then that classes' superclass, and so on * until {@code java.lang.Object} is reached. If a mapping is found anywhere * within the superclass hierarchy, then the mapped {@code Class} is * returned. Otherwise, the value returned by {@code getDefaultMapping()} is * returned. * * @param obj * the {@code Object} whose {@code Class's} internal mapping will * be returned * @return the {@code Class} that is mapped internally to the specified key * {@code Class} * @see #getDefaultMapping() * @see #addClassMapping(Object, Class) * @see #removeClassMapping(Object) */ public Class getClassMapping(Object obj) { Class key = obj == null ? null : obj.getClass(); return getClassMapping(key); } /** * Returns the {@code Class} associated with the specified {@code Class}. * If {@code key} is {@code null}, this method will return the value * retrieved from {@code getDefaultMapping()}. If no mapping has been * defined for the specified {@code Class}, then it's superclass is * checked, and then that classes' superclass, and so on until * {@code java.lang.Object} is reached. If a mapping is found anywhere * within the superclass hierarchy, then the mapped {@code Class} is * returned. Otherwise, the value returned by {@code getDefaultMapping()} is * returned. * * @param key * the {@code Class} whose internal mapping will be returned * @return the {@code Class} that is mapped internally to the specified * {@code key} * @see #getDefaultMapping() * @see #addClassMapping(Class, Class) * @see #removeClassMapping(Class) */ public Class getClassMapping(Class key) { if (key == null) { return defaultClass; } Class value = null; synchronized (classes) { for (Class c = key; c != null && value == null; c = c .getSuperclass()) { value = (Class) classes.get(c); } } return value == null ? defaultClass : value; } /** * Returns the {@code Object} instance associated with the specified * {@code Class}. If {@code key} is {@code null}, this method will return * the value retrieved from {@code getDefaultInstance()}. If no mapping has * been defined for the specified {@code Class}, then it's superclass is * checked, and then that classes' superclass, and so on until * {@code java.lang.Object} is reached. If an instance mapping is found * anywhere within the superclass hierarchy, then the mapped {@code Object} * is returned. Otherwise, the value returned by * {@code getDefaultInstance()} is returned. * * @param key * the {@code Class} whose internal mapping will be returned * @return the {@code Object} instance that is mapped internally to the * specified {@code key} * @see #getDefaultInstance() * @see #addClassMapping(Class, Class, Object) * @see #removeClassMapping(Class) */ public Object getClassInstance(Class key) { if (key == null) { return defaultInstance; } Object value = null; synchronized (instances) { for (Class c = key; c != null && value == null; c = c .getSuperclass()) { value = instances.get(c); } } return value == null ? defaultInstance : value; } /** * Returns the default {@code Class} used for situations in which there is * no internal class mapping. This property is read-only and is initialized * within the {@code ClassMapping} constructor. * * @return the default {@code Class} used for situations in which there is * no internal class mapping. * @see #ClassMapping(Class, Object) */ public Class getDefaultMapping() { return defaultClass; } /** * Returns the default {@code Object} used for situations in which there is * no internal instance mapping. This property is read-only and is * initialized within the {@code ClassMapping} constructor. * * @return the default {@code Object} used for situations in which there is * no internal instance mapping. * @see #ClassMapping(Class, Object) */ public Object getDefaultInstance() { return defaultInstance; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy