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

com.servicerocket.confluence.randombits.supplier.core.annotate.AnnotatedSupplier Maven / Gradle / Ivy

There is a newer version: 2.5.12
Show newest version
package com.servicerocket.confluence.randombits.supplier.core.annotate;

import com.servicerocket.confluence.randombits.supplier.core.KeyHandler;
import com.servicerocket.confluence.randombits.supplier.core.KeyPattern;
import com.servicerocket.confluence.randombits.supplier.core.Supplier;
import com.servicerocket.confluence.randombits.supplier.core.SupplierContext;

import java.lang.reflect.Method;
import java.util.*;

/**
 * This is a base class for suppliers that wish to use annotated
 * methods to define the {@link com.servicerocket.confluence.randombits.supplier.core.KeyHandler} instances available for this
 * supplier. It does this by annotating the class and its methods with the following annotations:
 *
 * 
    *
  • {@link SupplierPrefix} - Added to a Supplier class to indicate the prefix value for all keys it handles.
  • *
  • {@link SupportedTypes} - Added to the Supplier class to indicate the supported types.
  • *
  • {@link SupplierKey} - Added to methods to indicate they handle specific key(s).
  • *
* * A method that is marked as a {@link SupplierKey} acts as a receiver for specific 'key' values that match the * rules for both the prefix and type identified by the {@link SupplierPrefix} and {@link SupportedTypes}. A method * marked with {@link SupplierKey} can have zero or more parameters. All parameters must be annotated with one * of the following: * *
    *
  • {@link KeyValue} - The object being evaluated.
  • *
  • {@link KeyParam} - A String value that will receive a parameter as defined by the {@link SupplierKey} for that method.
  • *
  • {@link KeyContext} - A {@link com.servicerocket.confluence.randombits.supplier.core.SupplierContext} value which contains the current * keychain execution context.
  • *
* * Not every key handler method will need any or all of the above. The most common is {@link KeyValue}, but it's possible * for more 'global' supplier (eg. {@link com.servicerocket.confluence.randombits.supplier.core.special.ValueSupplier}) to not require one. * * The method may be public, private, static, final, or whatever. It is generally not recommended to make abstract methods * {@link SupplierKey}s because they would have to be re-annotated for every concrete method. A better option is to have * a concrete, annotated method that calls a protected abstract method that subclasses will implement. * * To create a Supplier, simply subclass this class and * create methods with the following pattern: * *

 * \@SupplierPrefix("my-supplier")
 * \@SupportedTypes(MyObject.class)
 * public class MySupplier extends AnnotatedSupplier {
 *
 *     \@SupplierKey("simple value")
 *     public String getSimpleKey( @KeyContext SupplierContext context ) throws SupplierException {
 *         MyObject value = context.getCurrentValue(MyObject.class);
 *         return value.getSimpleValue();
 *     }
 *
 *     \@SupplierKey("with {a param}")
 *     private OtherObject getKeyWithParam( \@KeyValue MyObject value, \@KeyParam("a param") String aParam ) throws SupplierException {
 *         return value.with( aParam );
 *     }
 *
 *     \@SupplierKey("current date")
 *     public static getCurrentDate() {
 *         return new Date();
 *     }
 * }
 * 
*/ public abstract class AnnotatedSupplier implements Supplier { private final Set prefixes; private final Set> supportedTypes; private boolean prefixRequired; private List keyDetails; private boolean nullAllowed; public AnnotatedSupplier() { Set prefixes = new HashSet(); findPrefixes( prefixes, getClass() ); if ( prefixes.size() == 0 ) throw new IllegalStateException( "AnnotatedSupplier instances using the default constructor must provide a @SupplierPrefix annotation to determine the prefix." ); this.prefixes = Collections.unmodifiableSet( prefixes ); Set> supportedTypes = new HashSet>(); nullAllowed = findSupportedTypes( supportedTypes, getClass() ); this.supportedTypes = supportedTypes.size() == 0 ? null : Collections.unmodifiableSet( supportedTypes ); nullAllowed = nullAllowed || supportedTypes == null; initKeyHandlers(); } public AnnotatedSupplier( String prefix, boolean prefixRequired, Class[] supportedTypes, boolean allowNull ) { this( Collections.singleton( prefix ), prefixRequired, supportedTypes, allowNull ); } /** * Constructs an annotated supplier with the prefix and supported type details hard-wired. * Any {@link com.servicerocket.confluence.randombits.supplier.core.annotate.SupplierPrefix} or * {@link com.servicerocket.confluence.randombits.supplier.core.annotate.SupportedTypes} annotations will be ignored. * * @param prefixes The prefixes to allow. * @param prefixRequired true if the prefix must be used. * @param supportedTypes The list of types supported by this Supplier. If all types (even null) are supported, do not provide any values. * @param allowNull Is null allowed */ public AnnotatedSupplier( Set prefixes, boolean prefixRequired, Class[] supportedTypes, boolean allowNull ) { this.prefixes = Collections.unmodifiableSet( prefixes ); this.prefixRequired = prefixRequired; this.supportedTypes = asSet( supportedTypes ); this.nullAllowed = allowNull; initKeyHandlers(); } /** * Finds all supported types, checking other the supertypes for inherited types. * * @param typeSet The set of types supported. * @param type The type to check. * @return true if the supplier should allow null values. */ private boolean findSupportedTypes( Set> typeSet, Class type ) { SupportedTypes supportedTypes = type.getAnnotation( SupportedTypes.class ); boolean allowNull = false; if ( supportedTypes != null ) { typeSet.addAll( Arrays.asList( supportedTypes.value() ) ); if ( !supportedTypes.inherit() ) return supportedTypes.nullAllowed(); else allowNull = supportedTypes.nullAllowed(); } if ( type.getSuperclass() != null ) allowNull = allowNull || findSupportedTypes( typeSet, type.getSuperclass() ); return allowNull; } private void findPrefixes( Set prefixes, Class type ) { SupplierPrefix prefix = type.getAnnotation( SupplierPrefix.class ); if ( prefix != null ) { prefixes.addAll( Arrays.asList( prefix.value() ) ); if ( prefix.required() ) prefixRequired = true; if ( !prefix.inherit() ) return; } if ( type.getSuperclass() != null ) findPrefixes( prefixes, type.getSuperclass() ); } private Set asSet( T[] supportedTypes ) { if ( supportedTypes != null && supportedTypes.length > 0 ) { Set types = new HashSet(); types.addAll( Arrays.asList( supportedTypes ) ); return Collections.unmodifiableSet( types ); } else { return null; } } @Override public Set> getSupportedTypes() { return supportedTypes; } /** * This method is called to check that an object is fully supported * by this supplier. It should not have to check the type - the object must * be one of the Class values returned by {@link #getSupportedTypes()}. * * The default implementation simply returns true. * Subclasses should override this if they need to perform extra checks on * specific instances of a value before it is handled. * * @param value The value to check. * @return true if the value is supported. */ protected boolean checkSupportedValue( Object value ) { return value != null || nullAllowed; } @Override public boolean isNullAllowed() { return nullAllowed; } @Override public Set getPrefixes() { return prefixes; } @Override public boolean isPrefixRequired() { return prefixRequired; } /** * Retrieves the set of {@link com.servicerocket.confluence.randombits.supplier.core.KeyHandler} instances for this supplier. * The first time the method is called, the set is created based on methods * that are annotated with {@link SupplierKey}. * * @return the collection of {@link com.servicerocket.confluence.randombits.supplier.core.KeyHandler} instances for this supplier. * @throws AnnotatedKeyDetailsException if there is a problem with any of the * annotated methods that are used to create the key details. */ @Override public Collection getKeyDetails() { return keyDetails; } private void initKeyHandlers() { List details = new ArrayList(); Class supplierClass = getClass(); while ( supplierClass != null ) { for ( Method method : supplierClass.getDeclaredMethods() ) { // Check if it's a key handler method. SupplierKey key = method.getAnnotation( SupplierKey.class ); if ( key != null ) { for ( String pattern : key.value() ) { KeyHandler handler = createKeyHandler( pattern, method ); details.add( handler ); } } } supplierClass = supplierClass.getSuperclass(); } Collections.sort( details, AnnotatedKeyHandler.COMPARATOR ); keyDetails = Collections.unmodifiableList( details ); } /** * Override this method to change the {@link KeyHandler} instance created for the method. * By default, this creates an instance of {@link AnnotatedKeyHandler}. * * @param pattern the key pattern. * @param method the method to execute. * @return the KeyHandler instance. */ protected KeyHandler createKeyHandler( String pattern, Method method ) { return new AnnotatedKeyHandler( KeyPattern.parse( pattern ), method, this ); } /** * This method is called when values for {link KeyValue} are converted to the target type. * By default, it simply asks the {@link SupplierContext} to do the conversion, but * subclasses may override this if they want to do more complex conversion in their suppliers. * If they do, they should also override the {@link #canGetValueAs(com.servicerocket.confluence.randombits.supplier.core.SupplierContext, Class)} * method to match the same conversion conditions. * * @param context The supplier context. * @param targetValueType The target type. * @param The target type. * @return The converted value, or null if none could be found. * @see #canGetValueAs(com.servicerocket.confluence.randombits.supplier.core.SupplierContext, Class) */ protected T getValueAs( SupplierContext context, Class targetValueType ) { return context.getValueAs( targetValueType ); } /** * This method is called when checking if the current value of the context can be * returned or converted to the specified type. Subclasses should override this to match * any custom conditions in a custom implementation of * {@link #getValueAs(com.servicerocket.confluence.randombits.supplier.core.SupplierContext, Class)} * * @param context The context to check again. * @param targetValueType The target value type. * @return true if the value can be converted. * @see #getValueAs(com.servicerocket.confluence.randombits.supplier.core.SupplierContext, Class) */ protected boolean canGetValueAs( SupplierContext context, Class targetValueType ) { return context.canGetValueAs( targetValueType ); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy