
com.oracle.bedrock.Options Maven / Gradle / Ivy
/*
* File: Options.java
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* The contents of this file are subject to the terms and conditions of
* the Common Development and Distribution License 1.0 (the "License").
*
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the License by consulting the LICENSE.txt file
* distributed with this file, or by consulting https://oss.oracle.com/licenses/CDDL
*
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file LICENSE.txt.
*
* MODIFICATIONS:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*/
package com.oracle.bedrock;
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.ArrayList;
import java.util.LinkedHashMap;
import java.util.Stack;
/**
* A collection of zero or more {@link Option}s, internally arranged as a map,
* keyed by the concrete type of each {@link Option} in the collection.
*
* Copyright (c) 2014. All Rights Reserved. Oracle Corporation.
* Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
*
* @author Brian Oliver
*/
public class Options
{
/**
* A map of the {@link Options} values, keyed by their concrete class.
*/
private final LinkedHashMap, Option> options;
/**
* Constructs a {@link Options} given an array of {@link Option}s
*
* @param options the {@link Option}s being managed
*/
public Options(Option... options)
{
this.options = new LinkedHashMap<>();
if (options != null)
{
for (Option option : options)
{
add(option);
}
}
}
/**
* A copy constructor that creates an {@link Options} containing all
* of the {@link Option}s from the specified {@link Options} instance.
*
* @param options the {@link Options} to copy
*/
public Options(Options options)
{
this.options = new LinkedHashMap<>();
addAll(options);
}
/**
* Constructs a new {@link Options} instance based on the specified {@link Option}s.
*
* @param options the {@link Option}s
*
* @return a new {@link Options} containing the specified {@link Option}s
*/
public static Options of(Option... options)
{
return new Options(options);
}
/**
* Obtains the {@link Option} for a specified concrete type from the collection.
*
* Should the {@link Option} not exist in the collection, an attempt is made
* to determine a default value using {@link #getDefaultFor(Class, Object...)}.
*
* @param classOfOption the concrete type of {@link Option} to obtain
* @param arguments the optional arguments for determining the default
* @param the type of value
*
* @return the {@link Option} of the specified type or if undefined, the
* suitable default value (or null
if one can't be determined)
*/
public T get(Class classOfOption,
Object... arguments)
{
if (classOfOption == null)
{
return null;
}
else
{
T option = (T) options.get(classOfOption);
if (option == null)
{
option = getDefaultFor(classOfOption, arguments);
add(option);
}
return option;
}
}
/**
* Obtains the {@link Option} of a specified concrete type from the collection. Should the type of
* {@link Option} not exist, the specified default is added to the collection and returned.
*
* @param the type of value
* @param the type of the default value
*
* @param classOfOption the type of {@link Option} to obtain
* @param defaultOption the {@link Option} to return if the specified type is not defined
*
* @return the option of the specified type or the default if it's not defined
*/
public T getOrDefault(Class classOfOption,
D defaultOption)
{
if (classOfOption == null)
{
return null;
}
else
{
T option = (T) options.get(classOfOption);
if (option == null && defaultOption != null)
{
option = defaultOption;
add(option);
}
return option;
}
}
/**
* Determines if an {@link Option} of the specified concrete type is in the
* collection.
*
* @param classOfOption the class of {@link Option}
*
* @return true
if the class of {@link Option} is in the {@link Options}
* false
otherwise
*/
public boolean contains(Class extends Option> classOfOption)
{
return get(classOfOption) != null;
}
/**
* Determines if the specified {@link Option} (and type) is in the {@link Options}.
*
* @param option the {@link Option}
*
* @return true
if the {@link Option} is defined,
* false
otherwise
*/
public boolean contains(Option option)
{
Class extends Option> classOfOption = option.getClass();
return get(classOfOption).equals(option);
}
/**
* Obtains an {@link Iterable} over all of the {@link Option}s in the collection
* that are an instance of the specified class.
*
* @param requiredClass the required class
* @param the type of option
*
* @return the options of the required class
*/
public Iterable getInstancesOf(Class requiredClass)
{
ArrayList result = new ArrayList<>();
for (Option option : options.values())
{
if (requiredClass.isInstance(option))
{
result.add((O) option);
}
if (option instanceof Option.Collector)
{
for (Object o : ((Option.Collector) option).getInstancesOf(requiredClass))
{
result.add((O) o);
}
}
}
return result;
}
/**
* Obtains the current collection of {@link Option}s as an array.
*
* @return an array of options
*/
public Option[] asArray()
{
Option[] aOptions = new Option[options.size()];
int i = 0;
for (Option option : options.values())
{
aOptions[i++] = option;
}
return aOptions;
}
@Override
public String toString()
{
StringBuilder bldrResult = new StringBuilder();
bldrResult.append("Options{");
boolean fFirst = true;
for (Option option : options.values())
{
if (fFirst)
{
fFirst = false;
}
else
{
bldrResult.append(", ");
}
bldrResult.append(option);
}
bldrResult.append("}");
return bldrResult.toString();
}
/**
* Constructs an {@link Options} collection given an array of {@link Option}s
*
* @param options the array of {@link Option}s
*
* @return an {@link Options} collection
*/
@SafeVarargs
public static Options from(Option... options)
{
return new Options(options);
}
/**
* Adds an option to the collection, replacing an
* existing {@link Option} of the same concrete type if one exists.
*
* @param option the {@link Option} to add
*
* @return the {@link Options} to permit fluent-style method calls
*/
public Options add(Option option)
{
if (option == null)
{
return this;
}
else
{
if (option instanceof Option.Collectable)
{
Option.Collectable collectable = (Option.Collectable) option;
// determine the type of Collector in which we'll collect the Collectable
Class extends Option> classOfCollector = getClassOf(collectable.getCollectorClass());
// attempt to locate an existing Collector
Option.Collector collector = (Option.Collector) options.get(classOfCollector);
// create a new collector if we don't have one
if (collector == null)
{
// attempt to create a new collector (using the @Option.Default annotation)
collector = (Option.Collector) getDefaultFor(classOfCollector);
}
if (collector == null)
{
throw new IllegalStateException("Failed to instantiate a default Collector of type "
+ classOfCollector + " for " + option);
}
else
{
// collect the collectable into the collector
collector = collector.with(collectable);
// replace the collector in the options
options.put(classOfCollector, collector);
}
}
else
{
// determine the class of option
Class extends Option> classOfOption = getClassOf(option);
// compose the option if it's composable
if (option instanceof ComposableOption)
{
Option existing = options.get(classOfOption);
if (existing != null)
{
option = ((ComposableOption) existing).compose((ComposableOption) option);
}
}
options.put(classOfOption, option);
}
return this;
}
}
/**
* Adds an {@link Option} to the collection if and only if an {@link Option} of the
* same type is not already present.
*
* @param option the {@link Option} to add
*
* @return the {@link Options} to permit fluent-style method calls
*/
public Options addIfAbsent(Option option)
{
Class extends Option> classOfOption = getClassOf(option);
if (!options.containsKey(classOfOption))
{
add(option);
}
return this;
}
/**
* Adds an array of {@link Option}s to the collection.
*
* @param options the {@link Option}s to add
*
* @return the {@link Options} to permit fluent-style method calls
*/
public Options addAll(Option... options)
{
if (options != null)
{
for (Option option : options)
{
add(option);
}
}
return this;
}
/**
* Adds all of the {@link Options} in the specified {@link Options}
* to this collection.
*
* @param options the {@link Options} to add
*
* @return the {@link Options} to permit fluent-style method calls
*/
public Options addAll(Options options)
{
for (Option option : options.asArray())
{
add(option);
}
return this;
}
/**
* Removes the specified type of {@link Option}
*
* @param classOfOption the class of {@link Option}
* @param the type of the {@link Option}
*
* @return true
if the {@link Option} was removed,
* false
otherwise
*/
public boolean remove(Class classOfOption)
{
if (classOfOption == null)
{
return false;
}
else
{
Option option = options.remove(getClassOf(classOfOption));
return option != null;
}
}
/**
* Removes the specific {@link Option}
*
* @param option the {@link Option} to remove
*
* @return true
if the {@link Option} was removed,
* false
otherwise, perhaps because it wasn't defined
*/
public boolean remove(Option option)
{
if (option == null)
{
return false;
}
else
{
if (option instanceof Option.Collectable)
{
Option.Collectable collectable = (Option.Collectable) option;
// determine the type of Collector
Class extends Option> classOfCollector = getClassOf(collectable.getCollectorClass());
// attempt to locate an existing Collector
Option.Collector collector = (Option.Collector) options.get(classOfCollector);
if (collector == null)
{
return false;
}
else
{
// remove the collectable
collector = collector.without(collectable);
// replace the collector
options.put(classOfCollector, collector);
return true;
}
}
else
{
Class extends Option> classOfOption = getClassOf(option);
Option existingOption = get(classOfOption);
if (existingOption == null ||!existingOption.equals(option))
{
return false;
}
else
{
options.remove(classOfOption);
return true;
}
}
}
}
/**
* Obtains the concrete type that directly implements / extends the specified {@link Option}.
*
* @param option the {@link Option}
*
* @return the concrete {@link Class} that directly extends / implements the {@link Option} interface
* or null
if the {@link Option} is null
*/
public static Class extends Option> getClassOf(Option option)
{
return option == null ? null : getClassOf(option.getClass());
}
/**
* Obtains the concrete type that directly implements / extends the {@link Option} interface,
* implemented by the specified class.
*
* @param classOfOption the class that somehow implements the {@link Option} interface
*
* @return the concrete {@link Class} that directly extends / implements the {@link Option} interface
* or null
if the specified {@link Class} doesn't implement {@link Option}
*/
public static Class extends Option> getClassOf(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 (Option.class.equals(interfaceClass)
|| ComposableOption.class.equals(interfaceClass)
|| Option.Collector.class.equals(interfaceClass))
{
// when the Option/ComposableOption 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 extends Option>) classOfOption;
}
else if (Option.class.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 Option (that is not a ComposableOption),
// and not just assume that the interfaceClass is directly implementing it
return (Class