cdc.util.enums.AbstractListDynamicEnum Maven / Gradle / Ivy
package cdc.util.enums;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import cdc.util.lang.Checks;
import cdc.util.lang.FailureReaction;
import cdc.util.lang.ImplementationException;
import cdc.util.lang.NotFoundException;
import cdc.util.lang.Operators;
import cdc.util.lang.UnexpectedValueException;
/**
* Dynamic enumeration of independent values.
*
* Each value has a unique name.
* This implementation supports these features:
*
* - {@link DagFeature#LOCKING}
*
- {@link DagFeature#CREATION} till locked.
*
- {@link DagFeature#REMOVAL} if allowed at creation time and till locked.
*
- {@link DagFeature#CONTENT_CHANGE} if allowed at creation time and till locked.
*
*
* A typical implementation should look like this:
*
{@code
* public final class Foo extends AbstractListDynamicEnum {
* public static final Support SUPPORT = support(Foo.class, Foo::new, Feature.RENAMING, ...);
*
* protected Foo(String value) {
* super(value);
* }
* }
* }
*
* @author Damien Carbonne
* @param The dynamic enum concrete type.
*
*/
public abstract class AbstractListDynamicEnum> implements ListDynamicEnum, Comparable {
private String name;
protected AbstractListDynamicEnum(String name) {
Checks.isNotNullOrEmpty(name, "name");
this.name = name;
}
protected final void setName(String name) {
this.name = name;
}
@Override
public final String getName() {
return name;
}
@Override
public String getQName() {
return getName();
}
@Override
public String toString() {
return getName();
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return this == obj;
}
@Override
public int compareTo(V o) {
return name.compareTo(o.getName());
}
/**
* Support interface describing standard methods expected for a dynamic enum.
*
* @author Damien Carbonne
*
* @param The enum type.
*/
public static interface Support> extends DynamicEnumSupport {
// Nothing to add
}
/**
* Interface used to create new instances of a dynamic enum.
*
* @author Damien Carbonne
*
* @param The dynamic enum type.
*/
@FunctionalInterface
public static interface Creator> {
public V create(String name);
}
@FunctionalInterface
public static interface Modifier> {
public void setName(V value,
String name);
}
public static > Support support(Class cls,
Predicate nameValidator,
Creator creator,
Modifier modifier,
DagFeature... features) {
Checks.isNotNull(cls, "cls");
Checks.isNotNull(nameValidator, "nameValidator");
Checks.isNotNull(creator, "creator");
return new SupportImpl<>(cls,
nameValidator,
creator,
modifier,
features);
}
/**
* Creates a support instance.
*
* @param The dynamic enum type.
* @param cls The dynamic enum class.
* @param nameValidator The predicate to check names validity.
* @param creator The dynamic enum factory
* @param features The features to enable.
* @return A new instance of Support for {@code }
*/
protected static > Support support(Class cls,
Predicate nameValidator,
Creator creator,
DagFeature... features) {
Checks.isNotNull(cls, "cls");
Checks.isNotNull(nameValidator, "nameValidator");
Checks.isNotNull(creator, "creator");
final Modifier modifier = (value,
name) -> value.setName(name);
return support(cls,
nameValidator,
creator,
modifier,
features);
}
protected static > Support support(Class cls,
Creator creator,
DagFeature... features) {
Checks.isNotNull(cls, "cls");
Checks.isNotNull(creator, "creator");
return support(cls,
AbstractDynamicEnumSupport.DEFAULT_NAME_VALIDATOR,
creator,
features);
}
/**
* Support Implementation.
*
* @author Damien Carbonne
*
* @param The dynamic enum type.
*/
private static final class SupportImpl> extends AbstractDynamicEnumSupport implements Support {
/** Ordered list of valid values. */
private final List validValues = new ArrayList<>();
/** Map from names to values. */
private final Map nameToValue = new HashMap<>();
private final Creator creator;
private final Modifier modifier;
private static final Predicate POSSIBLE_FEATURES = e -> {
switch (e) {
case CREATION:
case LOCKING:
case REMOVAL:
case CONTENT_CHANGE:
return true;
case REPARENTING:
return false;
default:
throw new UnexpectedValueException(e);
}
};
public SupportImpl(Class cls,
Predicate nameValidator,
Creator creator,
Modifier modifier,
DagFeature... features) {
super(cls,
nameValidator,
Checks.areAccepted(POSSIBLE_FEATURES, "features", features));
this.creator = creator;
this.modifier = modifier;
if (modifier == null) {
for (final DagFeature feature : features) {
if (feature == DagFeature.CONTENT_CHANGE) {
throw new IllegalArgumentException("Unexpected feature " + feature);
}
}
}
}
@Override
protected boolean isContained(V value) {
return validValues.contains(value);
}
@Override
public List getValues() {
return Collections.unmodifiableList(validValues);
}
@Override
public List getRoots() {
return getValues();
}
@Override
public List getChildren(V value) {
Checks.isNotNull(value, "value");
return Collections.emptyList();
}
@Override
public List getParents(V value) {
Checks.isNotNull(value, "value");
return Collections.emptyList();
}
@Override
public String getName(V value) {
return value == null ? null : value.getName();
}
@Override
public String getQName(V value) {
return getName(value);
}
@Override
public V valueOf(String qname,
FailureReaction reaction) {
return NotFoundException.onResult(nameToValue.get(qname),
EnumType.unknownQName(qname),
logger,
reaction,
null);
}
@Override
public V findOrCreate(String qname) {
V value = nameToValue.get(qname);
if (value == null) {
checkIsUnlocked();
checkNameIsValid(qname);
checkIsSupported(DagFeature.CREATION);
value = creator.create(qname);
if (!qname.equals(value.getName())) {
throw new ImplementationException("Unexpected name: '" + value.getName() + "'");
}
nameToValue.put(qname, value);
validValues.add(value);
fire(value, DagEventType.CREATED);
}
return value;
}
@Override
public void remove(V value) {
checkIsValid(value);
checkIsUnlocked();
checkIsSupported(DagFeature.REMOVAL);
validValues.remove(value);
nameToValue.remove(value.getName());
fire(value, DagEventType.REMOVED);
}
@Override
public void setName(V value,
String name) {
checkIsValid(value);
checkIsUnlocked();
checkIsSupported(DagFeature.CONTENT_CHANGE);
checkNameIsValid(name);
checkHasNoSiblingNamed(value, name);
if (!value.getName().equals(name)) {
nameToValue.remove(value.getName());
modifier.setName(value, name);
nameToValue.put(name, value);
fire(value, DagEventType.CONTENT_CHANGED);
}
}
@Override
public boolean isValid(V value) {
return value != null && nameToValue.containsKey(value.getName());
}
@Override
public boolean areEqual(V left,
V right) {
return Operators.equals(left, right);
}
@Override
public boolean isStrictlyOver(V left,
V right) {
return false;
}
}
}