/**
*
*/
package org.javabuilders;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.javabuilders.handler.IPropertyHandler;
import org.javabuilders.handler.ITypeAsValueHandler;
import org.javabuilders.handler.ITypeHandler;
import org.javabuilders.handler.ITypeHandlerAfterCreationProcessor;
import org.javabuilders.handler.ITypeHandlerFinishProcessor;
import org.javabuilders.layout.DefaultResize;
import org.javabuilders.util.BuilderUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Defines the metadata for a specific type
* Used when instantiating new objects
* @author Jacek Furmankiewicz
*/
public class TypeDefinition implements IKeyValueConsumer, IApplicable {
public static final Integer DEFAULT_DELAY_WEIGHT = 1000;
public final static Logger logger = LoggerFactory.getLogger(TypeDefinition.class);
/*
* STATIC HELPER METHODS
*/
/**
* Gets the delayed weight for a type. 0 means no delay
* @param handler Handler
* @param typeDefinitions Type definitions
* @return Delay weight
*/
public static Integer getDelayedWeight(ITypeHandler handler, Collection<TypeDefinition> typeDefinitions) {
Integer delayedWeight = 0;
root:
for(TypeDefinition def : typeDefinitions) {
Map<Class<?>,Integer> delayedClasses = def.getDelayedTypes();
for(Class<?> delayedClass : delayedClasses.keySet()) {
if (delayedClass.isAssignableFrom(handler.getApplicableClass())) {
delayedWeight = delayedClasses.get(delayedClass);
//take the delayed weight from the lowest object in the hierarchy
break root;
}
}
}
return delayedWeight;
}
/**
* Gets the delayed weight for a type's property. 0 means no delay
* @param handler Handler
* @param typeDefinitions Type definitions
* @return Delay weight
*/
public static Integer getDelayedWeight(ITypeHandler handler, String property, Collection<TypeDefinition> typeDefinitions) {
Integer delayedWeight = 0;
for(TypeDefinition def : typeDefinitions) {
Map<String,Integer> delayedProperties = def.getDelayedProperties();
if (delayedProperties.containsKey(property)) {
delayedWeight = delayedProperties.get(property);
break;
}
}
return delayedWeight;
}
/**
* Returns if a property cannot be localized or not
* @param propertyName Property name
* @param typeDefinitions Type definitions for the object instance
* @return true if localizable, false if not
*/
public static boolean isLocalizableProperty(String propertyName, Collection<TypeDefinition> typeDefinitions) {
boolean isLocalizable = false;
for(TypeDefinition def : typeDefinitions) {
if (def.isLocalized(propertyName)) {
isLocalizable = true;
break;
}
}
return isLocalizable;
}
/**
* Returns if the current parent of the node is of an allowed type
* @param parent Parrent
* @param typeDefinitions List of applicable type definitions
* @return
*/
public static boolean isParentAllowed(Object parent, Collection<TypeDefinition> typeDefinitions) {
boolean isAllowed = true;
for(TypeDefinition def : typeDefinitions) {
if (def.isParentAllowed(parent)) {
isAllowed = true;
break;
}
}
return isAllowed;
}
/**
* Returns the list of allowed parent types for a class
* @param config Builder config
* @param classType Class type
* @return Set of allowed parent types
*/
public static Set<Class<?>> getAllowedParents(BuilderConfig config, Class<?> classType) {
Set<Class<?>> allowed = new HashSet<Class<?>>();
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
for(Class<?> parentClass : def.getAllowedParents()) {
allowed.add(parentClass);
}
}
return allowed;
}
/**
* Returns a set of all the required keys
* @return List of required keys
*/
public static Set<String> getRequiredKeys(BuilderConfig config, Class<?> classType) {
Set<String> keys = new HashSet<String>();
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
keys.addAll(def.getRequiredKeys());
}
return keys;
}
/**
* Returns a set of all the required types
* @return List of required keys
*/
public static Set<Class<?>> getRequiredTypes(BuilderConfig config, Class<?> classType) {
Set<Class<?>> keys = new HashSet<Class<?>>();
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
keys.addAll(def.getRequiredTypes());
}
return keys;
}
/**
* Returns a list of all the defaults for this type
* @param config Config
* @param classType Class type
* @return List of defaults
*/
public static Map<String,Object> getDefaults(BuilderConfig config, Class<?> classType) {
Map<String,Object> map = new HashMap<String, Object>();
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
map.putAll(def.getDefaults());
}
return map;
}
/**
* Returns a set of all ignored properties
* @param config Config
* @param classType Class Type
* @return Set of ignored property names
*/
public static Set<String> getIgnored(BuilderConfig config, Class<?> classType) {
Set<String> ignored = new HashSet<String>();
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
ignored.addAll(def.getAllIgnored());
}
return ignored;
}
/**
* Gets the default resize policy for a type
* @param config Config
* @param classType Class type
* @return Default resize policy
*/
public static DefaultResize getDefaultResize(BuilderConfig config, Class<?> classType) {
DefaultResize resize = DefaultResize.NONE;
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
resize = def.getDefaultResize();
break;
}
return resize;
}
/**
* Gets the default resize policy for a type
* @param config Config
* @param parentClassType Parent class type (the type where the method will actually be invoked)
* @param classType Class type (the child type of the parent that may map to a method on the parent)
* @return Method (or null if none found)
*/
public static Method getTypeAsMethod(BuilderConfig config, Class<?> parentClassType, Class<?> classType) {
Method target = null;
Set<TypeDefinition> defs = config.getTypeDefinitions(parentClassType);
root:
for(TypeDefinition def : defs) {
Set<Class<?>> keySet = def.getTypesAsMethods().keySet();
for(Class<?> mappedType : keySet) {
if (mappedType.isAssignableFrom(classType)) {
target = def.getTypesAsMethods().get(mappedType);
break root;
}
}
}
return target;
}
/**
* Gets the list of finish-processors for a class
* @param config Config
* @param classType Class type
* @return List of finish processors
*/
public static List<ITypeHandlerFinishProcessor> getFinishProcessors(BuilderConfig config, Class<?> classType) {
List<ITypeHandlerFinishProcessor> list = new LinkedList<ITypeHandlerFinishProcessor>();
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
if (def.getFinishProcessor() != null) {
list.add(def.getFinishProcessor());
}
}
return list;
}
/**
* Gets the list of after creation-processors for a class
* @param config Config
* @param classType Class type
* @return List of finish processors
*/
public static List<ITypeHandlerAfterCreationProcessor> getAfterCreationProcessors(BuilderConfig config, Class<?> classType) {
List<ITypeHandlerAfterCreationProcessor> list = new LinkedList<ITypeHandlerAfterCreationProcessor>();
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
if (def.getAfterCreationProcessor() != null) {
list.add(def.getAfterCreationProcessor());
}
}
return list;
}
/**
* Returns the customized type handler for a type
* @param config Config
* @param classType Class type
* @return
*/
public static ITypeHandler getTypeHandler(BuilderConfig config, Class<?> classType) {
if (classType == null) {
throw new NullPointerException("classType cannot be null");
}
ITypeHandler handler = BuilderConfig.defaultTypeHandler;
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
if (def.getTypeHandler() != null) {
ITypeHandler defHandler = def.getTypeHandler();
if (defHandler.isApplicableToSubclasses()) {
handler = defHandler;
} else {
//applicable to this class only and not its descendants
if (classType.equals(def.getApplicableClass())) {
handler = defHandler;
}
}
break;
}
}
return handler;
}
/**
* Returns the customized property handler for a property
* @param config Config
* @param classType Class type
* @param property Property name
* @return Property handler
*/
public static IPropertyHandler getPropertyHandler(BuilderConfig config, Class<?> classType, String property) {
if (classType == null) {
throw new NullPointerException("classType cannot be null");
}
if (property == null) {
throw new NullPointerException("property cannot be null");
}
IPropertyHandler handler = BuilderConfig.defaultPropertyHandler;
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
if (def.getPropertyHandler(property) != null) {
handler = def.getPropertyHandler(property);
break;
}
}
return handler;
}
/**
* Gets the "type as value" handler, if defined
* @param config Config
* @param classType Class type
* @return "type as value" handler, or null if none
*/
public static ITypeAsValueHandler<? extends Object> getTypeAsValueHandler(BuilderConfig config, Class<?> classType) {
ITypeAsValueHandler<? extends Object> handler = null;
//special handling for enums - creates type as value handlers for them automatically
if (classType.isEnum()) {
TypeDefinition def = config.getTypeDefinition(classType);
if (def == null || def.getTypeAsValueHandler() == null) {
//create a default "type as value" handler for any enum that is encountered during the build process
config.forType(classType).valueHandler(createEnumTypeAsValueHandler(classType));
if (logger.isInfoEnabled()) {
logger.info("Created ITypeAsValueHandler instance for {}", classType.getName());
}
}
}
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
if (def.getTypeAsValueHandler() != null) {
handler = def.getTypeAsValueHandler();
break;
}
}
return handler;
}
/**
* Creates a "value as type" handler for an enum automatically
* @param enumClass Enum class
* @return Handler or null if class not an enum
*/
public static ITypeAsValueHandler<? extends Object> createEnumTypeAsValueHandler(final Class<?> enumClass) {
ITypeAsValueHandler<? extends Object> handler = null;
if (enumClass.isEnum()) {
Object[] constants = enumClass.getEnumConstants();
final Map<String,Object> values = new HashMap<String, Object>();
final StringBuilder regexBuilder = new StringBuilder();
for(Object constant : constants) {
//allow both the constant and it's more Java-like version to be used as an alias
values.put(constant.toString(), constant);
String shortConstant = getShortEnumConstant(constant);
values.put(shortConstant, constant);
if (regexBuilder.length() > 0) {
regexBuilder.append("|");
}
regexBuilder.append(constant).append("|").append(shortConstant);
}
final String regex = regexBuilder.toString();
//create new handler dynamically
handler = new ITypeAsValueHandler<Object>() {
public String getInputValueSample() {
return values.toString();
}
public String getRegex() {
return regex;
}
public Object getValue(BuildProcess process,Node node,String key,Object inputValue) throws BuildException{
return values.get(inputValue);
}
public Class<?> getApplicableClass() {
return enumClass;
}
};
}
return handler;
}
//returns the Java-like representation of an enum constant (e.g. "DIRECTION_LEFT" would become "directionLeft" and "DirectionLeft" would become "directonLeft")
public static String getShortEnumConstant(Object constant) {
String sConstant = constant.toString();
StringBuilder builder = new StringBuilder(sConstant.length());
if (sConstant.equals(sConstant.toUpperCase())) {
//constant style enum value, e.g. "DIRECTION_LEFT"
String[] parts = sConstant.split("_");
for(int p = 0; p < parts.length; p++) {
String part = parts[p];
if (p == 0) {
builder.append(part.toLowerCase());
} else {
if (part.length() > 0) {
builder.append(part.substring(0,1).toUpperCase());
if (part.length() > 1) {
builder.append(part.substring(1).toLowerCase());
}
}
}
}
} else {
//new-school style enum value, e.g. "DirectionLeft" => "directionLeft"
builder.append(sConstant.substring(0,1).toLowerCase());
builder.append(sConstant.substring(1));
}
return builder.toString();
}
/**
* Gets the real or mapped property value
* @param config Config
* @param classType Class type
* @return Property value
*/
public static Object getPropertyValue(BuilderConfig config, Class<?> classType, String propertyName, Object value) throws BuildException {
Object returnValue = value; //default
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
Map<String,? extends Object> mappedValues = def.getMappedPropertyValues(propertyName);
if (mappedValues != null) {
if (mappedValues.containsKey(value)) {
returnValue = mappedValues.get(value);
break;
} else {
//this property is flagged as mapped, but the value in the YAML file is not defined
throw new BuildException("Invalid value \"{0}\" for {1}.{2}. Allowed values are: {3}",value, classType.getSimpleName(),propertyName,
mappedValues.keySet());
}
}
}
return returnValue;
}
/**
* Gets the alias for a property, if defined
* @param config Config
* @param classType Class type
* @param propertyName Property name
* @return Alias or null if none defined
*/
public static String getPropertyForAlias(BuilderConfig config, Class<?> classType, String alias) {
String actual = null;
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
actual = def.getPropertyForAlias(alias);
if (actual != null) {
//stop on the first alias definition
break;
}
}
return actual;
}
/**
* Gets the value of a custom property, if defined
* @param config Config
* @param classType Class type
* @param propertyName Property name
* @return Custom property value or null if none defined
*/
public static Object getCustomProperty(BuilderConfig config, Class<?> classType, String propertyName) {
Object value = null;
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
if (def.getCustomProperties().containsKey(propertyName)) {
value = def.getCustomProperties().get(propertyName);
break;
}
}
return value;
}
/**
* Checks if a property should be treated as a list.
* @param config Config
* @param classType Class type
* @param propertyName Property name
* @return true/false
*/
public static Object isList(BuilderConfig config, Class<?> classType, String propertyName) {
boolean list = false;
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
if (def.isList(propertyName)) {
list = true;
break;
}
}
return list;
}
/**
* Tries to see if there is any class with static int constants mapped to a particular property name
* @param config Config
* @param classType Class type
* @param propertyName Property name
* @return Class or null if none found
*/
public static Class<?> getPropertyConstantsClass(BuilderConfig config, Class<?> classType, String propertyName) {
Class<?> constants = null;
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs) {
constants = def.getPropertyConstants(propertyName);
if (constants != null) {
break;
}
}
return constants;
}
/**
* Number of allowed children underneath a type
* @param config Config
* @param classType Type
* @return Number of children expected
*/
public static Map<Class<?>,int[]> getChildrenCardinality(BuilderConfig config, Class<?> classType) {
Map<Class<?>,int[]> children = new HashMap<Class<?>, int[]>();
Set<TypeDefinition> defs = config.getTypeDefinitions(classType);
for(TypeDefinition def : defs ) {
Map<Class<?>,int[]> current = def.getChildrenCardinality();
boolean override = def.isChildrenCardinalityOverriden();
if (override) {
//does not inherit cardinality from parents
if (current.size() > 0) {
children = current;
break;
}
} else {
//inherits cardinality from parent and extends it
for(Class<?> type : current.keySet()) {
//merge values across TypeDefinition inheritance
if (!children.containsKey(type)) {
children.put(type, current.get(type));
}
}
}
}
//assume everyone is a lead node if not defined
/*
if (children.size() == 0) {
children.put(Object.class, new int[]{0,0});
}
*/
return children;
}
/*
* INSTANCE METHODS
*/
private Class<?> applicableClass = null;
private Set<String> requiredKeys = new HashSet<String>();
private Set<Class<?>> requiredTypes = new HashSet<Class<?>>();
private Map<Class<?>,Integer> delayedTypes = new HashMap<Class<?>,Integer>();
private Map<String,Integer> delayedProperties = new HashMap<String,Integer>();
private Set<String> localizedProperties = new HashSet<String>();
private Set<Class<?>> allowedParents = new HashSet<Class<?>>();
private Map<String,Object> defaults = new HashMap<String,Object>();
private Set<String> ignoredProperties = new HashSet<String>();
private DefaultResize defaultResize = DefaultResize.NONE;
private Map<Class<?>,Method> typesAsMethods = new HashMap<Class<?>, Method>();
private ITypeHandlerFinishProcessor finishProcessor;
private ITypeHandlerAfterCreationProcessor afterCreationProcessor;
private ITypeAsValueHandler<? extends Object> typeAsValueHandler;
private ITypeHandler typeHandler;
private Map<String,String> propertyAliases = new HashMap<String, String>();
private Map<String,Map<String, ? extends Object>> mappedProperties = new HashMap<String, Map<String,? extends Object>>();
private Map<String,Object> customProperties = new HashMap<String,Object>();
private List<String> propertiesAsList = new ArrayList<String>();
private Map<String,Class<?>> propertyConstants = new HashMap<String, Class<?>>();
private Map<String,IPropertyHandler> propertyHandlers = new HashMap<String, IPropertyHandler>();
private Map<Class<?>,int[]> childrenCardinality = new HashMap<Class<?>, int[]>();
private boolean childrenCardinalityOverride = false;
/**
* Constructor
*/
public TypeDefinition(Class<?> applicableClass) {
if (applicableClass == null) {
throw new NullPointerException("applicableClass cannot be null");
}
this.applicableClass = applicableClass;
}
/**
* @return The applicable class
*/
public Class<?> getApplicableClass() {
return applicableClass;
}
/**
* Returns a set of all the required types
* @param defs
* @return
*/
public Set<Class<?>> getRequiredTypes(List<TypeDefinition> defs) {
Set<Class<?>> types = new HashSet<Class<?>>();
for(TypeDefinition def : defs) {
types.addAll(def.requiredTypes);
}
return types;
}
/**
* @return The required keys
*/
public Set<String> getRequiredKeys() {
return requiredKeys;
}
/**
* @return The required types
*/
public Set<Class<?>> getRequiredTypes() {
return requiredTypes;
}
/**
* Utility method to make adding required keys easier using the Builder pattern, e.g.
* <code>
* type.requires("name","text").requires(LayoutManager.class);
* </code>
* @param keys List of required keys
* @return Current instance, for use by Builder pattern
*/
public TypeDefinition requires(String...keys) {
for(String key : keys) {
getRequiredKeys().add(key);
}
return this;
}
/**
* Utility method to make adding required keys easier using the Builder pattern
* <code>
* type.requires("name","text").requires(LayoutManager.class);
* </code>
* @param types List of required class types
* @return Current instance, for use by Builder pattern
*/
public TypeDefinition requires(Class<?>...types) {
for(Class<?> type : types) {
getRequiredTypes().add(type);
}
return this;
}
/**
* Utility method to make adding delayed types easier using the Builder pattern
* <code>
* type.requires("name","text").requires(LayoutManager.class).delay(LayoutManager.class);
* </code>
* @param types List of required class types
* @return Current instance, for use by Builder pattern
*/
public TypeDefinition delay(int delayWeight, Class<?>...types) {
for(Class<?> type : types) {
getDelayedTypes().put(type,delayWeight);
}
return this;
}
/**
* Utility method to make adding delayed types easier using the Builder pattern
* <code>
* type.requires("name","text").requires(LayoutManager.class).delay(LayoutManager.class);
* </code>
* @param delayWeight Delay weight. Allows to control the order in which propertes are delayed. The ones with the
* lower weight get executed first.
* @param types List of required class types
* @return Current instance, for use by Builder pattern
*/
public TypeDefinition delay(Class<?>...types) {
return delay(DEFAULT_DELAY_WEIGHT,types);
}
/**
* Utility method to make adding delayed properties easier using the Builder pattern
* <code>
* type.requires("name","text").requires(LayoutManager.class).delay("MigLayout");
* </code>
* @param delayWeight Delay weight. Allows to control the order in which propertes are delayed. The ones with the
* lower weight get executed first.
* @param properties List of required class types
* @return Current instance, for use by Builder pattern
*/
public TypeDefinition delay(int delayWeight, String...properties) {
for(String property : properties) {
getDelayedProperties().put(property, delayWeight);
}
return this;
}
/**
* Utility method to make adding delayed properties easier using the Builder pattern
* <code>
* type.requires("name","text").requires(LayoutManager.class).delay("MigLayout");
* </code>
* @param properties List of required class types
* @return Current instance, for use by Builder pattern
*/
public TypeDefinition delay(String...properties) {
return delay(DEFAULT_DELAY_WEIGHT,properties);
}
/**
* Gets the list of types whose processing should be delayed till others are processed
* first, including their delay weights. <br/>
* Typical example is a LayoutManager, which should only be processed after all
* the other components are instantiated.
* @return The delayed types
*/
public Map<Class<?>,Integer> getDelayedTypes() {
return delayedTypes;
}
/**
* Gets the list of properties whose processing should be delayed till others are processed
* first, including their delay weights <br/>
* Typical example is a LayoutManager, which should only be processed after all
* the other components are instantiated.
* @return The delayed types list
*/
public Map<String,Integer> getDelayedProperties() {
return delayedProperties;
}
/**
* IKeyValueConsumer implementation.<br/>
* Returns the list of keys that are consumed during the creation of a type<br/>
* (e.g. the 'name' and 'text' keys are always consumed when creating a new JButton)
*/
public Set<String> getConsumedKeys() {
return requiredKeys;
}
/**
* Defines a property as being potentially localizable (e.g. "text", "title", etc.)
* @param propertyNames
* @return Same instance, for use in Builder pattern
*/
public TypeDefinition localize(String... propertyNames) {
for(String propertyName : propertyNames) {
localize(propertyName);
}
return this;
}
/**
* Defines a property as being potentially localizable (e.g. "text", "title", etc.)
* @param propertyName
* @return Same instance, for use in Builder pattern
*/
public TypeDefinition localize(String propertyName) {
localizedProperties.add(propertyName);
return this;
}
/**
* Returns if a property was defined as being potentially localizable
* @param propertyName
* @return
*/
public boolean isLocalized(String propertyName) {
return localizedProperties.contains(propertyName);
}
/**
* Defines a property as one that is ignored (i.e. processed differently somehow)
* @param propertyNames Property names to ignore
* @return Current type definition, for use in Builder pattern
*/
public TypeDefinition ignore(String... propertyNames) {
for (String propertyName : propertyNames) {
ignore(propertyName);
}
return this;
}
/**
* Defines a property as one that is ignored (i.e. processed differently somehow)
* @param propertyName Property name
* @return Current type definition, for use in Builder pattern
*/
public TypeDefinition ignore(String propertyName) {
ignoredProperties.add(propertyName);
return this;
}
/**
* Returns if a property should be ignored and not processed
* @param propertyName Property name
* @return True if ignored, false if to be processed
*/
public boolean isIgnored(String propertyName) {
return ignoredProperties.contains(propertyName);
}
/**
* Returns all ignored properties
* @return Set of properties
*/
public Set<String> getAllIgnored() {
return ignoredProperties;
}
/**
* Defines the parent that is allowed to contain the current type
* (e.g. a JMenuBar can only be created underneath a JFrame)
* @param parentTypes Parent class types
* @return Same instance, for use in Builder pattern
*/
public TypeDefinition allowParent(Class<?>... parentTypes) {
for(Class<?> parentType : parentTypes) {
if (parentType == null) {
throw new NullPointerException("parentType cannot be null");
}
allowedParents.add(parentType);
}
return this;
}
/**
* Returns the list of allowed parent types
* @return Allowed parent types
*/
public Set<Class<?>> getAllowedParents() {
return allowedParents;
}
/**
* Returns if the parent instance is allowed for this type
* @param parent Parent instance
* @return True if allowed, false if not
*/
public boolean isParentAllowed(Object parent) {
if (parent == null) {
throw new NullPointerException("parent cannot be null");
}
boolean allowed = false;
for(Class<?> classType : allowedParents) {
if (classType.isAssignableFrom(parent.getClass())){
allowed = true;
break;
}
}
return allowed;
}
/**
* Adds a default value for this type.
* @param property Property name
* @param value Value. If null, will try to remove it from the defaults
* @return Same instance, for use in Builder pattern
*/
public TypeDefinition defaultValue(String property, Object value) {
if (property == null) {
throw new NullPointerException("property cannot be null");
}
if (value == null) {
defaults.remove(property);
} else {
defaults.put(property, value);
}
return this;
}
/**
* Allows setting of the default resize policy
* @param resize Resize policy
* @return Type definition, for use in Builder pattern
*/
public TypeDefinition defaultResize(DefaultResize resize) {
this.defaultResize = resize;
return this;
}
/**
* @return Default resize policy
*/
public DefaultResize getDefaultResize() {
return this.defaultResize;
}
/**
* Returns a list of all the defaults
* @return List of defaults for this type
*/
public Map<String,Object> getDefaults() {
return defaults;
}
/**
* Maps a type to a method that that type should be passed into as a parameter, e.g.
* <code>"MigLayout: ..."<code>
* should automatically map to <code>setLayoutManager()</code>
* @param type Type
* @param methodName Method to call
* @return this
*/
public TypeDefinition typeAsMethod(Class<?> type, String methodName) {
Set<Method> methods = new HashSet<Method>(Arrays.asList(getApplicableClass().getMethods()));
methods.addAll(Arrays.asList(getApplicableClass().getDeclaredMethods()));
Method target = null;
for(Method method : methods) {
if (method.getName().equals(methodName) && method.getParameterTypes().length == 1 &&
method.getParameterTypes()[0].isAssignableFrom(type)) {
target = method;
break;
}
}
return typeAsMethod(type, target);
}
/**
* Maps a type to a method that that type should be passed into as a parameter, e.g.
* <code>"MigLayout: ..."<code>
* should automatically map to <code>setLayoutManager()</code>
* @param type Type
* @param method Method to call
* @return this
*/
public TypeDefinition typeAsMethod(Class<?> type, Method method) {
BuilderUtils.validateNotNullAndNotEmpty("method", method);
method.setAccessible(true);
typesAsMethods.put(type,method);
return this;
}
/**
* @return Methods that the types maps to
*/
public Map<Class<?>,Method> getTypesAsMethods() {
return typesAsMethods;
}
/**
* Gets mapped property values (ie. when a value is an alias, e.g. "JFrame.EXIT_ON_CLOSE" is really just an int value of 1")
* @param propertyName Property name
* @return Map of mapped key/value [airs
*/
public Map<String,? extends Object> getMappedPropertyValues(String propertyName) {
return mappedProperties.get(propertyName);
}
/**
* Sets the mapped property values for a particular property
* @param propertyName Property name
* @param values Map of values
* @return This
*/
public TypeDefinition asMapped(String propertyName, Map<String, ? extends Object> values) {
mappedProperties.put(propertyName, values);
return this;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return applicableClass.getName();
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return applicableClass.hashCode();
}
/**
* @return the finish processor
*/
public ITypeHandlerFinishProcessor getFinishProcessor() {
return finishProcessor;
}
/**
* @param finishProcessor the finish processor to set
* @return This
*/
public TypeDefinition finishProcessor(ITypeHandlerFinishProcessor finishProcessor) {
this.finishProcessor = finishProcessor;
return this;
}
/**
* @return the after creation processor
*/
public ITypeHandlerAfterCreationProcessor getAfterCreationProcessor() {
return afterCreationProcessor;
}
/**
* @param finishProcessor the finish processor to set
* @return This
*/
public TypeDefinition afterCreationProcessor(ITypeHandlerAfterCreationProcessor processor) {
this.afterCreationProcessor =processor;
return this;
}
/**
* @param typeHandler Type handler (takes care of creating a brand new instance, if the constructor call logic needs to be customized)
* @return This
*/
public TypeDefinition typeHandler(ITypeHandler typeHandler) {
if (typeHandler == null) {
throw new NullPointerException("typeHandler cannot be null");
}
if (typeHandler.getApplicableClass() == null) {
throw new NullPointerException("ITypeHandler.getApplicableClass() cannot be null");
}
if (this.applicableClass.isAssignableFrom(typeHandler.getApplicableClass())) {
this.typeHandler = typeHandler;
return this;
} else {
throw new BuildException("Type handler {0} is not valid to handle type {1}", typeHandler, applicableClass);
}
}
/**
* @return Type handler
*/
public ITypeHandler getTypeHandler() {
return this.typeHandler;
}
/**
* @return the typeAsValueHandler
*/
public ITypeAsValueHandler<? extends Object> getTypeAsValueHandler() {
return typeAsValueHandler;
}
/**
* @param typeAsValueHandler the typeAsValueHandler to set
*/
public TypeDefinition valueHandler(ITypeAsValueHandler<? extends Object> typeAsValueHandler) {
this.typeAsValueHandler = typeAsValueHandler;
return this;
}
/**
* Property alias
* @param propertyName
* @param alias
* @return
*/
public TypeDefinition propertyAlias(String propertyName, String alias) {
propertyAliases.put(alias,propertyName);
return this;
}
/**
* @param alias Property alias
* @return The real property the alias represents or null if none found
*/
public String getPropertyForAlias(String alias) {
return propertyAliases.get(alias);
}
/**
* @return Custom properties
*/
public TypeDefinition customProperty(String propertyName, Object value) {
getCustomProperties().put(propertyName,value);
return this;
}
/**
* @return Custom properties
*/
public Map<String,Object> getCustomProperties() {
return customProperties;
}
/**
* Defines a property that should automatically be converted to a list of
* particular type
* @param propertyNames
* @param data.getType()
* @return
*/
public TypeDefinition asList(String... propertyNames) {
for(String propertyName : propertyNames) {
propertiesAsList.add(propertyName);
}
return this;
}
/**
* Returns if a property is supposed to be a list
* @param propertyName Property name
* @return true/false
*/
public boolean isList(String propertyName) {
return propertiesAsList.contains(propertyName);
}
/**
* Maps a class with static int constants to a particular property. Workaround for various bad APIs
* @param propertyName Property name
* @param constantsClass Constants class for it
* @return This
*/
public TypeDefinition propertyConstants(String propertyName, Class<?> constantsClass) {
propertyConstants.put(propertyName, constantsClass);
return this;
}
/**
* @param propertyName
* @return
*/
public Class<?> getPropertyConstants(String propertyName) {
return propertyConstants.get(propertyName);
}
/**
* Defines property handlers for this type
* @param handlers Property handlers
* @return This
*/
public TypeDefinition propertyHandler(IPropertyHandler...handlers) {
for(IPropertyHandler handler : handlers) {
if (handler == null) {
throw new NullPointerException("handler cannot be null");
}
//register the handler for each of the keys it is supposed to consume
for(String key : handler.getConsumedKeys()) {
this.propertyHandlers.put(key, handler);
}
}
return this;
}
/**
* @param property Property name
* @return Property handler if found or null if not
*/
public IPropertyHandler getPropertyHandler(String property) {
return propertyHandlers.get(property);
}
/**
* Controls if the children cardinalities are inherited from the parents or not
* @param override Override parent children cardinalities
* @return This
*/
public TypeDefinition childrenOverride(boolean override) {
this.childrenCardinalityOverride = override;
return this;
}
/**
* Defines how many children are expected under this type in a build file
* @param children Children
* @return Builder
*/
public TypeDefinition children(Class<?> type, int min, int max) {
int[] cardinality = new int[]{min,max};
childrenCardinality.put(type, cardinality);
return this;
}
/**
* Defines how many children are expected under this type in a build file
* @param children Children
* @return Builder
*/
public TypeDefinition children(Class<?> type, int exact) {
return children(type,exact,exact);
}
/**
* Defines how many children are expected under this type in a build file
* @param children Children
* @return Builder
*/
public TypeDefinition children(int min, int max) {
return children(Object.class,min,max);
}
/**
* Defines how many children are expected under this type in a build file
* @param children Children
* @return Builder
*/
public TypeDefinition children(int exact) {
return children(Object.class,exact,exact);
}
/**
* @return Number of children allowed under this type
*/
public Map<Class<?>,int[]> getChildrenCardinality() {
return this.childrenCardinality;
}
/**
* @return True if overrides, false if inherits children cardinality from parents and extends it
*/
public boolean isChildrenCardinalityOverriden() {
return this.childrenCardinalityOverride;
}
}