package tc.oc.pgm.xml.property; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.Inject; import com.google.common.collect.Sets; import com.google.inject.assistedinject.Assisted; import org.jdom2.Element; import tc.oc.commons.core.util.Optionals; import tc.oc.commons.core.util.Pair; import tc.oc.pgm.xml.InvalidXMLException; import tc.oc.pgm.xml.Node; import tc.oc.pgm.xml.NodeSplitter; import tc.oc.pgm.xml.parser.PrimitiveParser; import tc.oc.pgm.xml.validate.Validation; import static tc.oc.commons.core.exception.LambdaExceptionUtils.rethrowConsumer; import static tc.oc.commons.core.exception.LambdaExceptionUtils.rethrowFunction; public class PropertyBuilder<T, Self extends PropertyBuilder<T, Self>> { protected final Element parent; protected final Optional<String> name; protected final Set<String> aliases = new HashSet<>(); protected NodeSplitter splitter = NodeSplitter.ATOM; protected final PrimitiveParser<? extends T> parser; protected final List<Validation<? super T>> validations = new ArrayList<>(); protected boolean attributes = true; protected boolean elements = true; @Inject public PropertyBuilder(@Assisted Element parent, @Assisted String name, PrimitiveParser<T> parser) { this(parent, Optional.of(name), parser); } private PropertyBuilder(Element node, Optional<String> name, PrimitiveParser<T> parser) { this.parent = node; this.name = name; this.parser = parser; } public Self alias(String... aliases) { return alias(Arrays.asList(aliases)); } public Self alias(Collection<String> aliases) { this.aliases.addAll(aliases); return self(); } public Self validate(Validation<? super T> validation) { validations.add(validation); return self(); } public Self split(NodeSplitter splitter) { this.splitter = splitter; return self(); } public Self attributes(boolean attributes) { this.attributes = attributes; return self(); } public Self elements(boolean elements) { this.elements = elements; return self(); } public Optional<T> optional() throws InvalidXMLException { final List<Pair<Node, String>> nodes = splitNodes().collect(Collectors.toList()); switch(nodes.size()) { case 0: return Optional.empty(); case 1: return Optional.of(parseAndValidate(nodes.get(0).first, nodes.get(0).second)); default: throw new InvalidXMLException("Multiple values given for unique property '" + name + "'", parent); } } public T optional(T def) throws InvalidXMLException { return optional().orElse(def); } public T required() throws InvalidXMLException { return optional().orElseThrow(() -> new InvalidXMLException("Missing required property '" + name + "'", parent)); } public Stream<T> multi() throws InvalidXMLException { return splitNodes().map(rethrowFunction(node -> parseAndValidate(node.first, node.second))); } protected Self self() { return (Self) this; } protected T parseAndValidate(Node node, String text) throws InvalidXMLException { final T value = parser.parse(node, text); validations.forEach(rethrowConsumer(v -> v.validate(value, node))); return value; } protected Stream<String> splitNode(Node node) throws InvalidXMLException { return splitter.split(node); } protected Stream<Node> findNodes() { final Set<String> names = Sets.union(Optionals.toSet(this.name), aliases); if(names.isEmpty()) { return Stream.of(Node.of(parent)); } Stream<Node> nodes = Stream.empty(); if(attributes) { nodes = Stream.concat(nodes, Node.attributes(parent, names)); } if(elements) { nodes = Stream.concat(nodes, Node.elements(parent, names)); } return nodes; } protected Stream<Pair<Node, String>> splitNodes() { return findNodes().flatMap(rethrowFunction(node -> splitNode(node).map(text -> Pair.of(node, text)))); } }