diff --git a/src/ts/@loafer/pouches/PouchMetadataElement.ts b/src/ts/@loafer/pouches/PouchMetadataElement.ts new file mode 100644 index 0000000..11b4e4f --- /dev/null +++ b/src/ts/@loafer/pouches/PouchMetadataElement.ts @@ -0,0 +1,9 @@ +export interface PouchMetadataElement { + /** + * Return the configuration source {@code Object} for this metadata element + * (may be {@code null}). + */ + getSource(): any; +} + +export default PouchMetadataElement; diff --git a/src/ts/@loafer/pouches/factory/config/ConstructorArgumentValues.ts b/src/ts/@loafer/pouches/factory/config/ConstructorArgumentValues.ts new file mode 100644 index 0000000..bce972b --- /dev/null +++ b/src/ts/@loafer/pouches/factory/config/ConstructorArgumentValues.ts @@ -0,0 +1,578 @@ +import { + ClassType, + PropertyType, +} from '@loafer/core/constants/types'; + +import { + Assert, +} from '@loafer/core/util'; + +import { + PouchMetadataElement, +} from '@loafer/pouches'; + +import { + HierarchicalPouchFactory, + PouchFactory, +} from '@loafer/pouches/factory'; + +import { + PouchDefinition, + PouchExpressionResolver, + SingletonPouchRegistry, +} from '@loafer/pouches/factory/config'; + + + + +export class ConstructorArgumentValues { + private readonly indexedArgumentValues: Map = new Map(); + private readonly genericArgumentValues: ValueHolder[] = []; + + /** + * Deep copy constructor. + * @param original the ConstructorArgumentValues to copy + */ + public constructor(original?: ConstructorArgumentValues) { + this.addArgumentValues(original); + } + + + /** + * Copy all given argument values into this object, using separate holder + * instances to keep the values independent from the original object. + *

Note: Identical ValueHolder instances will only be registered once, + * to allow for merging and re-merging of argument value definitions. Distinct + * ValueHolder instances carrying the same content are of course allowed. + */ + public addArgumentValues(other: ConstructorArgumentValues): void { + if (other != null) { + other.indexedArgumentValues.forEach((valueHolder, key, map) => { + this.addOrMergeIndexedArgumentValue(key, valueHolder.copy()); + }); + other.genericArgumentValues.filter((valueHolder, index, array) => { + return -1 === this.genericArgumentValues.indexOf(valueHolder); + }).forEach((valueHolder, index, array) => { + this.addOrMergeGenericArgumentValue(valueHolder.copy()); + }); + } + } + + + /** + * Add an argument value for the given index in the constructor argument list. + * @param index the index in the constructor argument list + * @param newValue the argument value in the form of a ValueHolder + */ + public addIndexedArgumentValue(index: number, value: ValueHolder): void; + /** + * Add an argument value for the given index in the constructor argument list. + * @param index the index in the constructor argument list + * @param value the argument value + * @param type the type of the constructor argument + */ + public addIndexedArgumentValue(index: number, value: any, type?: ClassType): void { + Assert.isTrue(index >= 0, 'Index must not be negative'); + + let newValue: ValueHolder; + if (!(value instanceof ValueHolder)) { + newValue = new ValueHolder(value, type); + } + Assert.notNull(newValue, 'ValueHolder must not be null'); + this.addOrMergeIndexedArgumentValue(index, newValue); + } + + /** + * Add an argument value for the given index in the constructor argument list, + * merging the new value (typically a collection) with the current value + * if demanded: see {@link org.springframework.beans.Mergeable}. + * @param key the index in the constructor argument list + * @param newValue the argument value in the form of a ValueHolder + */ + private addOrMergeIndexedArgumentValue(key: number, newValue: ValueHolder): void { + let currentValue: ValueHolder = this.indexedArgumentValues.get(key); + if (currentValue !== undefined && newValue.getValue() instanceof Mergeable) { + let mergeable: Mergeable = newValue.getValue(); + if (mergeable.isMergeEnabled()) { + newValue.setValue(mergeable.merge(currentValue.getValue())); + } + } + this.indexedArgumentValues.set(key, newValue); + } + + /** + * Check whether an argument value has been registered for the given index. + * @param index the index in the constructor argument list + */ + public hasIndexedArgumentValue(index: number): boolean { + return this.indexedArgumentValues.has(index); + } + + /** + * Get argument value for the given index in the constructor argument list. + * @param index the index in the constructor argument list + * @param requiredType the type to match (can be {@code null} to match + * untyped values only) + * @return the ValueHolder for the argument, or {@code null} if none set + */ + public getIndexedArgumentValue(index: number, requiredType: ClassType): ValueHolder { + return getIndexedArgumentValue(index, requiredType, null); + } + + /** + * Get argument value for the given index in the constructor argument list. + * @param index the index in the constructor argument list + * @param requiredType the type to match (can be {@code null} to match + * untyped values only) + * @param requiredName the type to match (can be {@code null} to match + * unnamed values only, or empty String to match any name) + * @return the ValueHolder for the argument, or {@code null} if none set + */ + + public getIndexedArgumentValue(index: number, requiredType: ClassType, requiredName: PropertyType): ValueHolder { + Assert.isTrue(index >= 0, "Index must not be negative"); + ValueHolder valueHolder = this.indexedArgumentValues.get(index); + if (valueHolder != null && + (valueHolder.getType() == null || + (requiredType != null && ClassUtils.matchesTypeName(requiredType, valueHolder.getType()))) && + (valueHolder.getName() == null || "".equals(requiredName) || + (requiredName != null && requiredName.equals(valueHolder.getName())))) { + return valueHolder; + } + return null; + } + + /** + * Return the map of indexed argument values. + * @return unmodifiable Map with Integer index as key and ValueHolder as value + * @see ValueHolder + */ + public Map getIndexedArgumentValues() { + return Collections.unmodifiableMap(this.indexedArgumentValues); + } + + + /** + * Add a generic argument value to be matched by type. + *

Note: A single generic argument value will just be used once, + * rather than matched multiple times. + * @param value the argument value + */ + public void addGenericArgumentValue(Object value) { + this.genericArgumentValues.add(new ValueHolder(value)); + } + + /** + * Add a generic argument value to be matched by type. + *

Note: A single generic argument value will just be used once, + * rather than matched multiple times. + * @param value the argument value + * @param type the type of the constructor argument + */ + public void addGenericArgumentValue(Object value, String type) { + this.genericArgumentValues.add(new ValueHolder(value, type)); + } + + /** + * Add a generic argument value to be matched by type or name (if available). + *

Note: A single generic argument value will just be used once, + * rather than matched multiple times. + * @param newValue the argument value in the form of a ValueHolder + *

Note: Identical ValueHolder instances will only be registered once, + * to allow for merging and re-merging of argument value definitions. Distinct + * ValueHolder instances carrying the same content are of course allowed. + */ + public void addGenericArgumentValue(ValueHolder newValue) { + Assert.notNull(newValue, "ValueHolder must not be null"); + if (!this.genericArgumentValues.contains(newValue)) { + addOrMergeGenericArgumentValue(newValue); + } + } + + /** + * Add a generic argument value, merging the new value (typically a collection) + * with the current value if demanded: see {@link org.springframework.beans.Mergeable}. + * @param newValue the argument value in the form of a ValueHolder + */ + private void addOrMergeGenericArgumentValue(ValueHolder newValue) { + if (newValue.getName() != null) { + for (Iterator it = this.genericArgumentValues.iterator(); it.hasNext();) { + ValueHolder currentValue = it.next(); + if (newValue.getName().equals(currentValue.getName())) { + if (newValue.getValue() instanceof Mergeable) { + Mergeable mergeable = (Mergeable) newValue.getValue(); + if (mergeable.isMergeEnabled()) { + newValue.setValue(mergeable.merge(currentValue.getValue())); + } + } + it.remove(); + } + } + } + this.genericArgumentValues.add(newValue); + } + + /** + * Look for a generic argument value that matches the given type. + * @param requiredType the type to match + * @return the ValueHolder for the argument, or {@code null} if none set + */ + + public ValueHolder getGenericArgumentValue(Class requiredType) { + return getGenericArgumentValue(requiredType, null, null); + } + + /** + * Look for a generic argument value that matches the given type. + * @param requiredType the type to match + * @param requiredName the name to match + * @return the ValueHolder for the argument, or {@code null} if none set + */ + + public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName) { + return getGenericArgumentValue(requiredType, requiredName, null); + } + + /** + * Look for the next generic argument value that matches the given type, + * ignoring argument values that have already been used in the current + * resolution process. + * @param requiredType the type to match (can be {@code null} to find + * an arbitrary next generic argument value) + * @param requiredName the name to match (can be {@code null} to not + * match argument values by name, or empty String to match any name) + * @param usedValueHolders a Set of ValueHolder objects that have already been used + * in the current resolution process and should therefore not be returned again + * @return the ValueHolder for the argument, or {@code null} if none found + */ + + public ValueHolder getGenericArgumentValue( Class requiredType, String requiredName, Set usedValueHolders) { + for (ValueHolder valueHolder : this.genericArgumentValues) { + if (usedValueHolders != null && usedValueHolders.contains(valueHolder)) { + continue; + } + if (valueHolder.getName() != null && !"".equals(requiredName) && + (requiredName == null || !valueHolder.getName().equals(requiredName))) { + continue; + } + if (valueHolder.getType() != null && + (requiredType == null || !ClassUtils.matchesTypeName(requiredType, valueHolder.getType()))) { + continue; + } + if (requiredType != null && valueHolder.getType() == null && valueHolder.getName() == null && + !ClassUtils.isAssignableValue(requiredType, valueHolder.getValue())) { + continue; + } + return valueHolder; + } + return null; + } + + /** + * Return the list of generic argument values. + * @return unmodifiable List of ValueHolders + * @see ValueHolder + */ + public List getGenericArgumentValues() { + return Collections.unmodifiableList(this.genericArgumentValues); + } + + + /** + * Look for an argument value that either corresponds to the given index + * in the constructor argument list or generically matches by type. + * @param index the index in the constructor argument list + * @param requiredType the parameter type to match + * @return the ValueHolder for the argument, or {@code null} if none set + */ + + public ValueHolder getArgumentValue(index: number, Class requiredType) { + return getArgumentValue(index, requiredType, null, null); + } + + /** + * Look for an argument value that either corresponds to the given index + * in the constructor argument list or generically matches by type. + * @param index the index in the constructor argument list + * @param requiredType the parameter type to match + * @param requiredName the parameter name to match + * @return the ValueHolder for the argument, or {@code null} if none set + */ + + public ValueHolder getArgumentValue(index: number, Class requiredType, String requiredName) { + return getArgumentValue(index, requiredType, requiredName, null); + } + + /** + * Look for an argument value that either corresponds to the given index + * in the constructor argument list or generically matches by type. + * @param index the index in the constructor argument list + * @param requiredType the parameter type to match (can be {@code null} + * to find an untyped argument value) + * @param requiredName the parameter name to match (can be {@code null} + * to find an unnamed argument value, or empty String to match any name) + * @param usedValueHolders a Set of ValueHolder objects that have already + * been used in the current resolution process and should therefore not + * be returned again (allowing to return the next generic argument match + * in case of multiple generic argument values of the same type) + * @return the ValueHolder for the argument, or {@code null} if none set + */ + + public ValueHolder getArgumentValue(index: number, Class requiredType, String requiredName, Set usedValueHolders) { + Assert.isTrue(index >= 0, "Index must not be negative"); + ValueHolder valueHolder = getIndexedArgumentValue(index, requiredType, requiredName); + if (valueHolder == null) { + valueHolder = getGenericArgumentValue(requiredType, requiredName, usedValueHolders); + } + return valueHolder; + } + + /** + * Return the number of argument values held in this instance, + * counting both indexed and generic argument values. + */ + public int getArgumentCount() { + return (this.indexedArgumentValues.size() + this.genericArgumentValues.size()); + } + + /** + * Return if this holder does not contain any argument values, + * neither indexed ones nor generic ones. + */ + public boolean isEmpty() { + return (this.indexedArgumentValues.isEmpty() && this.genericArgumentValues.isEmpty()); + } + + /** + * Clear this holder, removing all argument values. + */ + public void clear() { + this.indexedArgumentValues.clear(); + this.genericArgumentValues.clear(); + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ConstructorArgumentValues)) { + return false; + } + ConstructorArgumentValues that = (ConstructorArgumentValues) other; + if (this.genericArgumentValues.size() != that.genericArgumentValues.size() || + this.indexedArgumentValues.size() != that.indexedArgumentValues.size()) { + return false; + } + Iterator it1 = this.genericArgumentValues.iterator(); + Iterator it2 = that.genericArgumentValues.iterator(); + while (it1.hasNext() && it2.hasNext()) { + ValueHolder vh1 = it1.next(); + ValueHolder vh2 = it2.next(); + if (!vh1.contentEquals(vh2)) { + return false; + } + } + for (Map.Entry entry : this.indexedArgumentValues.entrySet()) { + ValueHolder vh1 = entry.getValue(); + ValueHolder vh2 = that.indexedArgumentValues.get(entry.getKey()); + if (!vh1.contentEquals(vh2)) { + return false; + } + } + return true; + } + + @Override + public int; hashCode() { + int hashCode = 7; + for (ValueHolder valueHolder : this.genericArgumentValues) { + hashCode = 31 * hashCode + valueHolder.contentHashCode(); + } + hashCode = 29 * hashCode; + for (Map.Entry entry : this.indexedArgumentValues.entrySet()) { + hashCode = 31 * hashCode + (entry.getValue().contentHashCode() ^ entry.getKey().hashCode()); + } + return hashCode; + } + + + +} + + +/** + * Holder for a constructor argument value, with an optional type + * attribute indicating the target type of the actual constructor argument. + */ +export class ValueHolder implements PouchMetadataElement { + + + private Object value; + + + private String type; + + + private String name; + + + private Object source; + + private boolean converted = false; + + + private Object convertedValue; + + /** + * Create a new ValueHolder for the given value. + * @param value the argument value + */ + public ValueHolder( Object value) { + this.value = value; + } + + /** + * Create a new ValueHolder for the given value and type. + * @param value the argument value + * @param type the type of the constructor argument + */ + public ValueHolder( Object value, String type) { + this.value = value; + this.type = type; + } + + /** + * Create a new ValueHolder for the given value, type and name. + * @param value the argument value + * @param type the type of the constructor argument + * @param name the name of the constructor argument + */ + public ValueHolder( Object value, String type, String name) { + this.value = value; + this.type = type; + this.name = name; + } + + /** + * Set the value for the constructor argument. + * @see PropertyPlaceholderConfigurer + */ + public void setValue(; Object value) { + this.value = value; + } + + /** + * Return the value for the constructor argument. + */ + + public Object; getValue(); { + return this.value; + } + + /** + * Set the type of the constructor argument. + */ + public void setType(; String; type;) { + this.type = type; + } + + /** + * Return the type of the constructor argument. + */ + + public String; getType(); { + return this.type; + } + + /** + * Set the name of the constructor argument. + */ + public void setName(; String; name; ) { + this.name = name; + } + + /** + * Return the name of the constructor argument. + */ + + public String; getName(); { + return this.name; + } + + /** + * Set the configuration source {@code Object} for this metadata element. + *

The exact type of the object will depend on the configuration mechanism used. + */ + public void setSource(; Object; source; ) { + this.source = source; + } + +@Override + + public Object; getSource(); { + return this.source; + } + + /** + * Return whether this holder contains a converted value already ({@code true}), + * or whether the value still needs to be converted ({@code false}). + */ + public synchronized; boolean; isConverted(); { + return this.converted; + } + + /** + * Set the converted value of the constructor argument, + * after processed type conversion. + */ + public synchronized; void setConvertedValue(; Object; value; ) { + this.converted = (value != null); + this.convertedValue = value; + } + + /** + * Return the converted value of the constructor argument, + * after processed type conversion. + */ + + public synchronized; Object; getConvertedValue(); { + return this.convertedValue; + } + + /** + * Determine whether the content of this ValueHolder is equal + * to the content of the given other ValueHolder. + *

Note that ValueHolder does not implement {@code equals} + * directly, to allow for multiple ValueHolder instances with the + * same content to reside in the same Set. + */ + private boolean; contentEquals(ValueHolder other); { + return (this === other || + (ObjectUtils.nullSafeEquals(this.value, other.value) && ObjectUtils.nullSafeEquals(this.type, other.type))); + } + + /** + * Determine whether the hash code of the content of this ValueHolder. + *

Note that ValueHolder does not implement {@code hashCode} + * directly, to allow for multiple ValueHolder instances with the + * same content to reside in the same Set. + */ + private int; contentHashCode(); { + return ObjectUtils.nullSafeHashCode(this.value) * 29 + ObjectUtils.nullSafeHashCode(this.type); + } + + /** + * Create a copy of this ValueHolder: that is, an independent + * ValueHolder instance with the same contents. + */ + public ValueHolder; copy(); { + ValueHolder; copy = new ValueHolder(this.value, this.type, this.name); + copy.setSource(this.source); + return copy; + } +} + + +export default ConstructorArgumentValues; diff --git a/src/ts/@loafer/pouches/factory/config/PouchDefinition.ts b/src/ts/@loafer/pouches/factory/config/PouchDefinition.ts index df21c38..fde5392 100644 --- a/src/ts/@loafer/pouches/factory/config/PouchDefinition.ts +++ b/src/ts/@loafer/pouches/factory/config/PouchDefinition.ts @@ -8,15 +8,122 @@ import { } from '@loafer/pouches/constants/types'; interface PouchDefinition { - readonly Clazz: ClassType; - Scope: PouchScope; - Qualifier: PropertyType; - readonly PostConstruct: Set; - readonly PreDestroy: Set; - addPostConstruct(postConstruct: string): void; - addPreDestroy(preDestroy: string): void; - isSingleton(): boolean; - isTransient(): boolean; + + /** + * The name of the parent definition of this pouch definition, if any. + */ + ParentName: PropertyType; + /** + * The current pouch class name of this pouch definition. + *

Note that this does not have to be the actual class name used at runtime, in + * case of a child definition overriding/inheriting the class name from its parent. + * Also, this may just be the class that a factory method is called on, or it may + * even be empty in case of a factory pouch reference that a method is called on. + * Hence, do not consider this to be the definitive pouch type at runtime but + * rather only use it for parsing purposes at the individual pouch definition level. + * @see #getParentName() + * @see #getFactoryPouchName() + * @see #getFactoryMethodName() + */ + PouchClassName: string; + /** + * Override the target scope of this pouch, specifying a new scope name. + * @see #SCOPE_SINGLETON + * @see #SCOPE_PROTOTYPE + */ + Scope: string; + /** + * Whether this pouch should be lazily initialized. + *

If {@code false}, the pouch will get instantiated on startup by pouch + * factories that perform eager initialization of singletons. + */ + LazyInit: boolean; + /** + * The names of the pouchs that this pouch depends on being initialized. + * The pouch factory will guarantee that these pouchs get initialized first. + */ + DependsOn: PropertyType[]; + /** + * Whether this pouch is a candidate for getting autowired into some other pouch. + *

Note that this flag is designed to only affect type-based autowiring. + * It does not affect explicit references by name, which will get resolved even + * if the specified pouch is not marked as an autowire candidate. As a consequence, + * autowiring by name will nevertheless inject a pouch if the name matches. + */ + AutowireCandidate: boolean; + /** + * Whether this pouch is a primary autowire candidate. + *

If this value is {@code true} for exactly one pouch among multiple + * matching candidates, it will serve as a tie-breaker. + */ + Primary: boolean; + /** + * Specify the factory pouch to use, if any. + * This the name of the pouch to call the specified factory method on. + * @see #setFactoryMethodName + */ + FactoryPouchName: PropertyType; + /** + * Specify a factory method, if any. This method will be invoked with + * constructor arguments, or with no arguments if none are specified. + * The method will be invoked on the specified factory pouch, if any, + * or otherwise as a static method on the local pouch class. + * @see #setFactoryPouchName + * @see #setPouchClassName + */ + FactoryMethodName: PropertyType; + /** + * Return the constructor argument values for this pouch. + *

The returned instance can be modified during pouch factory post-processing. + * @return the ConstructorArgumentValues object (never {@code null}) + */ + getConstructorArgumentValues(): ConstructorArgumentValues; + /** + * Return the property values to be applied to a new instance of the pouch. + *

The returned instance can be modified during pouch factory post-processing. + * @return the MutablePropertyValues object (never {@code null}) + */ + getPropertyValues(): MutablePropertyValues; + /** + * Return whether this a Singleton, with a single, shared instance + * returned on all calls. + * @see #SCOPE_SINGLETON + */ + isSingleton(): boolean; + + /** + * Return whether this a Prototype, with an independent instance + * returned for each call. + * @see #SCOPE_PROTOTYPE + */ + isPrototype(): boolean; + + /** + * Return whether this pouch is "abstract", that is, not meant to be instantiated. + */ + isAbstract(): boolean; + + // Read-only attributes + + /** + * Return a human-readable description of this pouch definition. + */ + getDescription(): string; + + /** + * Return a description of the resource that this pouch definition + * came from (for the purpose of showing context in case of errors). + */ + getResourceDescription(): string; + + /** + * Return the originating PouchDefinition, or {@code null} if none. + * Allows for retrieving the decorated pouch definition, if any. + *

Note that this method returns the immediate originator. Iterate through the + * originator chain to find the original PouchDefinition as defined by the user. + */ + getOriginatingPouchDefinition(): PouchDefinition; } export default PouchDefinition; + diff --git a/src/ts/@loafer/pouches/index.ts b/src/ts/@loafer/pouches/index.ts index 9df4309..97694b6 100644 --- a/src/ts/@loafer/pouches/index.ts +++ b/src/ts/@loafer/pouches/index.ts @@ -1 +1,3 @@ export * from './PouchesException'; +export * from './PouchMetadataElement'; +