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

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 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 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 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 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 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 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 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 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 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) 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

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 (using the provided arguments if supplied), failing that, * looking for and evaluating the annotated "public static U value = ...;" * field, failing that, looking for and evaluating the annotated public * constructor (using the provided arguments if supplied) 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 classOfOption the class * @param arguments the optional arguments for static methods / constructors * @param the type of value * * @return a default value or null if a default can't be * determined */ protected T getDefaultFor(Class classOfOption, Object... arguments) { if (classOfOption == null) { return null; } else { for (Method method : classOfOption.getMethods()) { int modifiers = method.getModifiers(); if (method.getAnnotation(Default.class) != null && method.getParameterTypes().length == arguments.length && Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && classOfOption.isAssignableFrom(method.getReturnType())) { try { return (T) method.invoke(null, arguments); } catch (Exception e) { // carry on... perhaps we can use another approach? } } } } for (Field field : classOfOption.getFields()) { int modifiers = field.getModifiers(); if (field.getAnnotation(Default.class) != null && Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && classOfOption.isAssignableFrom(field.getType())) { try { return (T) field.get(null); } catch (Exception e) { // carry on... perhaps we can use another approach? } } } for (Constructor constructor : classOfOption.getConstructors()) { int modifiers = constructor.getModifiers(); if (constructor.getAnnotation(Default.class) != null && Modifier.isPublic(modifiers) && constructor.getParameterTypes().length == arguments.length) { try { return (T) constructor.newInstance(arguments); } catch (Exception e) { // carry on... perhaps we can use another approach? } } } // couldn't find a default so let's return null return null; } /** * 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, Object...) */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD}) public @interface Default { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy