com.oracle.coherence.common.util.Options Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* 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 extends T> 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 extends T> 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
{
}
}