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

cdc.util.enums.AbstractForestDynamicEnum Maven / Gradle / Ivy

package cdc.util.enums;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
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 values organized as a forest (several trees).
 * 

* Each value has a unique qualified 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. *
  • {@link DagFeature#REPARENTING} if allowed at creation time and till locked. *
*

* A typical implementation should look like this: *

{@code
 * public final class Foo extends AbstractForestDynamicEnum {
 *     public static final Support SUPPORT = support(Foo.class, Foo::new, Feature.RENAMING, ...);
 *
 *     protected Foo(Foo parent,
 *                   String value) {
 *         super(parent, value);
 *     }
 * }
 * }
* * @author Damien Carbonne * @param The dynamic enum concrete type. */ public abstract class AbstractForestDynamicEnum> implements ForestDynamicEnum, Comparable { private String name; private String qname; private V parent; final List children = new ArrayList<>(); /** * Character used to separate local names in paths. */ public static final char SEPARATOR = '/'; protected AbstractForestDynamicEnum(V parent, String name) { Checks.isNotNullOrEmpty(name, "name"); this.parent = parent; this.name = name; if (parent == null) { qname = name; } else { @SuppressWarnings("unchecked") final V tmp = (V) this; parent.children.add(tmp); qname = parent.getQName() + SEPARATOR + name; } } protected final void setParent(V parent) { this.parent = parent; } @Override public final V getParent() { return parent; } @Override public final List getChildren() { return Collections.unmodifiableList(children); } protected final void setName(String name) { this.name = name; if (parent == null) { qname = name; } else { qname = parent.getQName() + SEPARATOR + name; } } /** * @return The local name. */ @Override public final String getName() { return name; } /** * @return The qualified name (path). */ @Override public final String getQName() { return qname; } @Override public String toString() { return getQName(); } @Override public int hashCode() { return getQName().hashCode(); } @Override public boolean equals(Object obj) { return this == obj; } @Override public int compareTo(V o) { return getQName().compareTo(o.getQName()); } /** * Support interface describing standard methods expected for a dynamic enum. * * @author Damien Carbonne * * @param The enum type. */ public static interface Support> extends DynamicEnumSupport { public V findOrCreate(V parent, String path); public void setParent(V value, V parent); } /** * 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(V parent, String name); } public static interface Modifier> { public void setName(V value, String name); public void setParent(V value, V parent); public void addChild(V value, V child); public void removeChild(V value, V child); } 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 = new Modifier() { @Override public void setName(V value, String name) { value.setName(name); } @Override public void setParent(V value, V parent) { value.setParent(parent); } @Override public void addChild(V value, V child) { value.children.add(child); } @Override public void removeChild(V value, V child) { value.children.remove(child); } }; 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 roots. */ private final List roots = new ArrayList<>(); /** Ordered list of valid values. */ private final List validValues = new ArrayList<>(); /** Map from qnames to values. */ private final Map qnameToValue = 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: case REPARENTING: return true; default: throw new UnexpectedValueException(e); } }; private final Consumer refreshQName; protected 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 || feature == DagFeature.REPARENTING) { throw new IllegalArgumentException("Unexpected feature " + feature); } } } refreshQName = v -> { qnameToValue.remove(v.getQName()); modifier.setName(v, v.getName()); qnameToValue.put(v.getQName(), v); }; } @Override protected boolean isContained(V value) { return validValues.contains(value); } @Override public List getValues() { return Collections.unmodifiableList(validValues); } @Override public String getName(V value) { return value == null ? null : value.getName(); } @Override public String getQName(V value) { return value == null ? null : value.getQName(); } @Override public List getRoots() { return Collections.unmodifiableList(roots); } @Override public List getChildren(V value) { return value.getChildren(); } @Override public List getParents(V value) { return value.getParent() == null ? Collections.emptyList() : Collections.unmodifiableList(Arrays.asList(value.getParent())); } @Override public V valueOf(String qname, FailureReaction reaction) { return NotFoundException.onResult(qnameToValue.get(qname), EnumType.unknownQName(qname), logger, reaction, null); } @Override public V findOrCreate(String qname) { V value = qnameToValue.get(qname); if (value == null) { checkIsUnlocked(); final int pos = qname.lastIndexOf(SEPARATOR); final V parent; final String name; if (pos < 0) { parent = null; name = qname; } else { parent = findOrCreate(qname.substring(0, pos)); name = qname.substring(pos + 1); } checkNameIsValid(name); value = creator.create(parent, name); if (!name.equals(value.getName()) || value.getParent() != parent) { throw new ImplementationException(getValueClass().getCanonicalName() + " Unexpected name '" + value.getName() + "' under '" + parent + "'"); } qnameToValue.put(qname, value); validValues.add(value); if (parent == null) { roots.add(value); } fire(value, DagEventType.CREATED); } return value; } @Override public V findOrCreate(V parent, String path) { if (parent == null) { return findOrCreate(path); } else { return findOrCreate(parent.getQName() + SEPARATOR + path); } } @Override public void setParent(V value, V parent) { checkIsValid(value); checkIsUnlocked(); checkIsSupported(DagFeature.REPARENTING); checkIsNotOverOrEqual(value, parent); if (value.getParent() != parent) { if (value.getParent() != null) { modifier.removeChild(value.getParent(), value); } else { roots.remove(value); } modifier.setParent(value, parent); if (parent == null) { roots.add(value); } else { modifier.addChild(parent, value); } // Update qualified names iterateUnder(value, refreshQName); fire(value, DagEventType.REPARENTED); } } @Override public void remove(V value) { checkIsValid(value); checkIsUnlocked(); checkIsSupported(DagFeature.REMOVAL); // Remove children first while (!value.getChildren().isEmpty()) { final V last = value.getChildren().get(value.getChildren().size() - 1); remove(last); } // Update parent or roots if (value.getParent() != null) { modifier.removeChild(value.getParent(), value); } else { roots.remove(value); } // Update caches validValues.remove(value); qnameToValue.remove(value.getQName()); 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)) { qnameToValue.remove(value.getQName()); modifier.setName(value, name); qnameToValue.put(value.getQName(), value); // Update children qualified names for (final V child : value.getChildren()) { iterateUnder(child, refreshQName); } fire(value, DagEventType.CONTENT_CHANGED); } } @Override public boolean isValid(V value) { return value != null && qnameToValue.containsKey(value.getQName()); } @Override public boolean areEqual(V left, V right) { return Operators.equals(left, right); } @Override public boolean isStrictlyOver(V left, V right) { return left != null && right != null && left.isStrictlyOver(right); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy