com.github.dakusui.floorplan.component.Attribute Maven / Gradle / Ivy
Show all versions of floorplan Show documentation
package com.github.dakusui.floorplan.component;
import com.github.dakusui.floorplan.resolver.Resolver;
import com.github.dakusui.floorplan.utils.InternalUtils;
import com.github.dakusui.floorplan.utils.ObjectSynthesizer;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import static com.github.dakusui.floorplan.resolver.Resolvers.nothing;
import static com.github.dakusui.floorplan.utils.Checks.requireArgument;
import static java.util.Objects.requireNonNull;
/**
* This interface is expected to be implemented by an {@code Enum} class that
* is defined for a certain component and enumerates attributes belonging to
* the component.
*/
public interface Attribute {
/**
* Returns a name of this attribute. Implementation can be given usually by {@code Enum}'s
* {@code name()} method. In case an {@code Attribute} is implemented by extending
* existing one, in other words instances of it are created by {@code Attribute#create(...)}
* methods, they will return values given to the methods as {@code name} argument.
*
* @return A name of this attribute.
*/
default String name() {
throw new UnsupportedOperationException();
}
@SuppressWarnings("unchecked")
default Resolver super A, ?> defaultValueResolver() {
return definition().defaultValueResolverFactory.apply(this);
}
/**
* Checks if a given value can become a value of this attribute. Typically,
* if the {@code value} is an instance of a class returned through {@code valueType()},
* {@code true} will be returned. This can be said the definition of this object is
* created by {@code ComponentSpec#property(Class<?> type)} method.
*
* For more detail, refer to methods in {@code ComponentSpec} class that return
* {@code Attribute.Bean.Builder} object.
*
* @param value A value to be tested.
* @return {@code true} - {@code value} can become a value of this attribute/ {@code false} - otherwise
* @see ComponentSpec
*/
default boolean test(Object value) {
return definition().constraint.test(value);
}
/**
* Returns a type of value to be held by this attribute.
* Note that the type returned by this method is not always equal to the type of the value returned
* by {@code Component#resolverFor} with this object if an arity of this attribute is an {@code ARRAY}, not an
* {@code ATOM}.
* In this case, a class object returned by this method represents a type of each element in the returned list
* of the method {@code Component#resolverFor}.
*
* @return Type of this attribute.
*/
default Class> valueType() {
return definition().valueType;
}
/**
* Returns a {@code ComponentSpec} object to which this attribute belongs.
*
* @param Type of this attribute
* @return A spec to which this attribute belongs.
*/
@SuppressWarnings("unchecked")
default ComponentSpec spec() {
return (ComponentSpec) definition().spec;
}
/**
* Returns a description of a constraint held by this object.
*
* @return A description of a constraint.
*/
default String describeConstraint() {
return definition().constraint.toString();
}
/**
* Returns more specialized one from {@code this} object and given {@code another}
* object.
*
* Being "more specialized" means defined in a subclass.
*
* If {@code this} and {@code another} have different names, an exception will
* be thrown.
*
* @param another Another object to be compared with this object.
* @param Type of this attribute.
* @return More specialized one from this and another.
*/
@SuppressWarnings("unchecked")
default Optional moreSpecialized(A another) {
requireNonNull(another);
requireArgument(this.name(), n -> Objects.equals(n, another.name()));
if (this.spec().attributeType().isAssignableFrom(another.spec().attributeType()))
return Optional.of(another);
if (another.spec().attributeType().isAssignableFrom(this.spec().attributeType()))
return Optional.of((A) this);
return Optional.empty();
}
default boolean isOptional() {
return definition().isOptional;
}
/**
* Returns a definition object of this attribute interface.
* Accessing this method from outside this interface is discouraged.
*
* @param Type of the attribute
* @param Type of the definition
* @return A definition object of this attribute.
*/
> B definition();
/**
* @param definition An object that holds contents of the attribute to be created.
* @param A type of attribute, represented by {@code attrType}.
* @return Created attribute.
*/
static A create(Definition definition) {
class Attr implements Attribute {
private String attrName = null;
@SuppressWarnings("unchecked")
@Override
public > B definition() {
return (B) definition;
}
public synchronized String toString() {
return attrName == null
? String.format("%s.(noname)@%d", spec().attributeType().getSimpleName(), System.identityHashCode(this))
: attrName;
}
public synchronized String name() {
if (attrName == null)
attrName = InternalUtils.determineAttributeName(definition.spec.attributeType(), this);
return attrName;
}
}
Attr fallback = new Attr();
return ObjectSynthesizer.builder(definition.spec.attributeType())
.handle(new ObjectSynthesizer.Handler.Builder(
method -> method.getName().equals("name") && method.getParameterCount() == 0)
.with((o, args) -> fallback.name()))
.fallbackTo(fallback)
.build()
.synthesize();
}
final class Definition {
/**
* Spec of the component to which this attribute belongs.
*/
final ComponentSpec spec;
/**
* A function to resolve a default value of an attribute.
*/
final Function> defaultValueResolverFactory;
/**
* A constraint to be satisfied by a value for the attribute to which this
* definition belongs.
*/
final Predicate