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

com.oracle.coherence.common.util.Options Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */
package com.oracle.coherence.common.util;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Stack;
import java.util.stream.Collectors;

/**
 * An immutable collection of zero or more values, typically called an options,
 * internally arranged as a map, keyed by the concrete type of each option in
 * the collection.
 *
 * @param   the base type of the options in the collection
 *
 * @author bko  2015.07.24
 */
public class Options
    {

    // ----- constructors ---------------------------------------------------

    /**
     * Constructs an empty {@link Options} collection.
     *
     * @param clsType the {@link Class} of the base type of the options
     *                in the collection
     */
    private Options(Class clsType)
        {
        m_mapOptions = new LinkedHashMap<>();
        m_clsType    = clsType;
        }

    /**
     * Constructs an {@link Options} collection based on an array of option
     * values.
     *
     *
     * @param clsType   the {@link Class} of the base type of the options
     *                  in the collection
     * @param aOptions  the array of options to add to the collection
     */
    private Options(Class clsType, T[] aOptions)
        {
        m_mapOptions = new LinkedHashMap<>();
        m_clsType    = clsType;

        addAll(aOptions);
        }

    // ----- Options methods ------------------------------------------------

    /**
     * Obtains the option for a specified concrete type from the collection.
     *
     * 

Should the option not exist in the collection, an attempt is made * to determine a suitable default based on the use of the {@link Default} * annotation in the specified class, firstly by looking for and evaluating * the annotated "public static U getter()" method, failing that, looking for * and evaluating the annotated "public static U value = ...;" field, failing * that, looking for an evaluating the annotated public no args constructor * and finally, failing that, looking for an annotated field on an enum * (assuming the class is an enum). Failing these approaches, * null is returned.

* * @param clzOption the concrete type of option to obtain * @param the type of value * * @return the option of the specified type or if undefined, the * suitable default value (or null if one can't be * determined) */ public U get(Class clzOption) { return get(clzOption, getDefaultFor(clzOption)); } /** * Obtains the option of a specified concrete type from the collection. *

* Should the type of option not exist, the specified default is returned. * * @param clzOption the type of option to obtain * @param optDefault the option to return if the specified type is not defined * @param the type of value * * @return the option of the specified type or * the default if it's not defined */ public U get(Class clzOption, U optDefault) { if (clzOption == null) { return null; } else { T option = m_mapOptions.get(clzOption); if (option == null) { return optDefault; } else { return (U) option; } } } /** * Determines if an option of the specified concrete type is in the * collection. * * @param clzOption the class of option * @param the type of option * * @return true if the class of option is in the {@link Options} * false otherwise */ public boolean contains(Class clzOption) { return get(clzOption) != null; } /** * Determines if the specified option (and type) is in the {@link Options}. * * @param option the option * * @return true if the options is defined, * false otherwise */ public boolean contains(T option) { if (option == null) { return false; } Class clzOption = getClassOf(option); Object value = get(clzOption); return value != null && value.equals(option); } /** * Obtains an {@link Iterable} over all of the options in the collection * that are an instance of the specified class. * * @param clz the required class * @param the type of option * * @return the options of the required class */ public Iterable getInstancesOf(Class clz) { return m_mapOptions.values() .stream() .filter(clz::isInstance) .map(value -> (O) value) .collect(Collectors.toCollection(LinkedList::new)); } /** * Obtains the current collection of options as an array. * * @return an array of options */ public T[] asArray() { T[] aOptions = (T[]) new Object[m_mapOptions.size()]; int i = 0; for (T option : m_mapOptions.values()) { aOptions[i++] = option; } return aOptions; } // ----- Object methods ------------------------------------------------- @Override public String toString() { StringBuilder bldrResult = new StringBuilder(); bldrResult.append("Options{"); boolean fFirst = true; for (T option : m_mapOptions.values()) { if (fFirst) { fFirst = false; } else { bldrResult.append(", "); } bldrResult.append(option); } bldrResult.append("}"); return bldrResult.toString(); } // ----- helper methods ------------------------------------------------- /** * Constructs an {@link Options} collection given an array of options * * @param clsType the {@link Class} of the base type of the options * in the collection * @param aOptions the array of options * * @param the type of options * * @return an {@link Options} collection */ @SafeVarargs public static Options from(Class clsType, T... aOptions) { return aOptions == null || aOptions.length == 0 ? empty() : new Options<>(clsType, aOptions); } /** * Constructs an empty {@link Options} collection * * @param the type of options * * @return an empty {@link Options} collection */ public static Options empty() { return EMPTY; } // ----- internal methods ----------------------------------------------- /** * Adds an option to the collection, replacing an * existing option of the same concrete type if one exists. * * @param option the option to add * * @return the {@link Options} to permit fluent-style method calls */ private Options add(T option) { Class clz = getClassOf(option); m_mapOptions.put(clz, option); return this; } /** * Adds an array of options to the collection, replacing * existing options of the same concrete type where they exist. * * @param aOptions the options to add * * @return the {@link Options} to permit fluent-style method calls */ private Options addAll(T[] aOptions) { if (aOptions != null) { for (T option : aOptions) { add(option); } } return this; } /** * Adds all of the options in the specified {@link Options} * to this collection, replacing existing options of the same concrete * type where they exist. * * @param options the {@link Options} to add * * @return the {@link Options} to permit fluent-style method calls */ private Options addAll(Options options) { for (T option : options.asArray()) { add(option); } return this; } /** * Obtains the concrete type of an option. * * @param option the option * * @return the concrete {@link Class} that directly extends / implements * the value interface * or null if the value is null */ private Class getClassOf(T option) { return option == null ? null : getClassOf(option.getClass()); } /** * Obtains the concrete type that directly implements / extends the {@link #m_clsType} * option {@link Class}. * * @param classOfOption the class that somehow implements or extends {@link #m_clsType} * * @return the concrete {@link Class} that directly extends / implements the {@link #m_clsType} * class or null if the specified {@link Class} doesn't implement or extend * the {@link #m_clsType} class */ private Class getClassOf(Class classOfOption) { if (m_clsType.equals(classOfOption)) { return (Class) classOfOption; } // the hierarchy of classes we've visited // (so that we can traverse it later to find non-abstract classes) Stack> hierarchy = new Stack<>(); while (classOfOption != null) { // remember the current class hierarchy.push(classOfOption); for (Class interfaceClass : classOfOption.getInterfaces()) { if (m_clsType.equals(interfaceClass)) { // when the option class is directly implemented by a class, // we return the first non-abstract class in the hierarchy. while (classOfOption != null && Modifier.isAbstract(classOfOption.getModifiers()) && !classOfOption.isInterface()) { classOfOption = hierarchy.isEmpty() ? null : hierarchy.pop(); } return (Class) (classOfOption == null ? null : classOfOption.isSynthetic() ? interfaceClass : classOfOption); } else if (m_clsType.isAssignableFrom(interfaceClass)) { // ensure that we have a concrete class in our hierarchy while (classOfOption != null && Modifier.isAbstract(classOfOption.getModifiers()) && !classOfOption.isInterface()) { classOfOption = hierarchy.isEmpty() ? null : hierarchy.pop(); } if (classOfOption == null) { // when the hierarchy is entirely abstract, we can't determine a concrete Option type return null; } else { // when the option is a super class of an interface, // we return the interface that's directly extending it. // TODO: we should search to find the interface that is directly // extending the option type and not just assume that the interfaceClass // is directly implementing it return (Class) interfaceClass; } } } classOfOption = classOfOption.getSuperclass(); } return null; } /** * Attempts to determine a "default" value for a given class. * *

Aan attempt is made to determine a suitable default based on the use * of the {@link Default} annotation in the specified class, firstly by * looking for and evaluating the annotated "public static U getter()" * method, failing that, looking for and evaluating the annotated * "public static U value = ...;" field, failing that, looking for an * evaluating the annotated public no args constructor and finally, failing * that, looking for an annotated field on an enum * (assuming the class is an enum). Failing these approaches, * null is returned.

* * @param clzOption the class * @param the type of value * * @return a default value or null if a default can't be * determined */ protected U getDefaultFor(Class clzOption) { if (clzOption == null) { return null; } else { for (Method method : clzOption.getMethods()) { int modifiers = method.getModifiers(); if (method.getAnnotation(Default.class) != null && method.getParameterCount() == 0 && Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && clzOption.isAssignableFrom(method.getReturnType())) { try { return (U) method.invoke(null); } catch (Exception e) { //carry on... perhaps we can use another approach? } } } } for (Field field : clzOption.getFields()) { int modifiers = field.getModifiers(); if (field.getAnnotation(Default.class) != null && Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && clzOption.isAssignableFrom(field.getType())) { try { return (U) field.get(null); } catch (Exception e) { // carry on... perhaps we can use another approach? } } } try { Constructor constructor = clzOption.getConstructor(); int modifiers = constructor.getModifiers(); if (constructor.getAnnotation(Default.class) != null && Modifier.isPublic(modifiers)) { try { return (U) constructor.newInstance(); } catch (Exception e) { //carry on... perhaps we can use another approach? } } } catch (NoSuchMethodException e) { // carry on... there's no no-args constructor } // couldn't find a default so let's return null return null; } // ----- data members --------------------------------------------------- /** * The map of the options, keyed by their concrete class. */ private LinkedHashMap, T> m_mapOptions; private Class m_clsType; // ----- constants ------------------------------------------------------ /** * A constant to represent an empty {@link Options} collection. */ private final static Options EMPTY = new EmptyOptions(); // ----- internal EmptyOptions class ------------------------------------ /** * An optimized {@link Options} implementation for representing empty * {@link Options}. * * @param the type of the {@link Options} */ private static final class EmptyOptions extends Options { // ----- constructors ----------------------------------------------- /** * Constructs an {@link EmptyOptions} */ public EmptyOptions() { super(null); } // ----- Options methods -------------------------------------------- @Override public U get(Class clzOption) { return getDefaultFor(clzOption); } @Override public U get(Class clzOption, U optDefault) { return optDefault; } @Override public boolean contains(Class clzOption) { return false; } @Override public boolean contains(T option) { return false; } @Override public Iterable getInstancesOf(Class clz) { return Collections.EMPTY_SET; } @Override public T[] asArray() { return (T[]) EMPTY; } // ----- Object methods --------------------------------------------- @Override public String toString() { return "EmptyOptions{}"; } // ----- constants -------------------------------------------------- /** * The empty array of options. */ private static final Object[] EMPTY = {}; } // ----- Default annotation --------------------------------------------- /** * Defines how an {@link Options} collection may automatically determine a * suitable default value for a specific class of option at runtime * when the said option does not exist in an {@link Options} collection. * * For example, the {@link Default} annotation can be used to specify that * a public static no-args method can be used to determine a default value. *

     * public class Color {
     *     ...
     *     @Options.Default
     *     public static Color getDefault() {
     *         ...
     *     }
     *     ...
     * }
     * 
* * Similarly, the {@link Default} annotation can be used to specify a * public static field to use for determining a default value. *

     * public class Color {
     *     ...
     *     @Options.Default
     *     public static Color BLUE = ...;
     *     ...
     * }
     * 
* * Alternatively, the {@link Default} annotation can be used to specify that * the public no-args constructor for a public class may be used for * constructing a default value. *

     * public class Color {
     *     ...
     *     @Options.Default
     *     public Color() {
     *         ...
     *     }
     *     ...
     * }
     * 
* * Lastly when used with an enum, the {@link Default} annotation * can be used to specify the default enum constant. *

     * public enum Color {
     *     RED,
     *
     *     GREEN,
     *
     *     @Options.Default
     *     BLUE;   // blue is the default color
     * }
     * 
* * @see Options#get(Class) */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD}) public @interface Default { } }