/* * #%L * Nazgul Project: nazgul-core-algorithms-tree-model * %% * Copyright (C) 2010 - 2017 jGuru Europe AB * %% * Licensed under the jGuru Europe AB license (the "License"), based * on Apache License, Version 2.0; you may not use this file except * in compliance with the License. * * You may obtain a copy of the License at * * http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% * */ package se.jguru.nazgul.core.algorithms.tree.model.node; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.jguru.nazgul.core.algorithms.api.Validate; import se.jguru.nazgul.core.algorithms.api.collections.predicate.Filter; import se.jguru.nazgul.core.algorithms.api.trees.node.MutableNode; import se.jguru.nazgul.core.algorithms.api.trees.node.Node; import se.jguru.nazgul.core.algorithms.api.trees.path.Path; import se.jguru.nazgul.core.algorithms.tree.model.path.StringPath; import se.jguru.nazgul.core.persistence.model.NazgulEntity; import se.jguru.nazgul.core.xmlbinding.api.XmlBinder; import se.jguru.nazgul.tools.validation.api.exception.InternalStateValidationException; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.MappedSuperclass; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Abstract persistable MutableNode implementation, which inherits persistence mechanics from NazgulEntity. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ @MappedSuperclass @XmlType(namespace = XmlBinder.CORE_NAMESPACE, propOrder = {"parent", "key"}) @XmlAccessorType(XmlAccessType.FIELD) public abstract class AbstractStringKeyNode<V extends Serializable> extends NazgulEntity implements MutableNode<String, V> { // Our Log private static final Logger log = LoggerFactory.getLogger(AbstractStringKeyNode.class); // Internal state private final transient Object lock = new Object(); /** * The required key within this AbstractStringKeyNode. */ @Basic(optional = false) @Column(nullable = false) @XmlElement(required = true) private String key; /** * The optional parent of this AbstractStringKeyNode. */ @Basic @Column @XmlElement(nillable = true) private MutableNode<String, V> parent; /** * JPA/JAXB-friendly constructor. */ public AbstractStringKeyNode() { } /** * Compound constructor, creating an instance wrapping the given data. * * @param key The key of this AbstractStringKeyNode. * @param parent The parent of this AbstractStringKeyNode, which must be another AbstractStringKeyNode. * @param <T> The concrete parent node subtype. */ public <T extends AbstractStringKeyNode<V>> AbstractStringKeyNode(@NotNull final String key, final T parent) { this.key = key; this.parent = parent; // Ensure that this node is added as a child to the supplied parent. if (parent != null) { parent.addChild(this); } } /** * {@inheritDoc} */ @Override public void clearParent() { if (parent != null) { // Remove this node from the children list of its parent. synchronized (lock) { // TODO: Send tree-changed event to parent? parent.getChildren().remove(this); parent = null; } } else { if (log.isWarnEnabled()) { log.warn("Cannot clear the parent node from a Root node. (It has no parent)."); } } } /** * {@inheritDoc} */ @Override public String getKey() { return key; } /** * {@inheritDoc} */ @Override public void setParent(final MutableNode<String, V> parent) { // Check sanity Validate.notNull(parent, "parent"); final MutableNode<String, V> oldParent = this.parent; // Add this MutableNode to the provided parent. synchronized (lock) { if (oldParent != null) { // TODO: Send tree-changed event to old parent? oldParent.getChildren().remove(this); } // TODO: Send tree-changed event to new parent? // Assign our internal state. this.parent = parent; final List<Node<String, V>> newParentChildren = parent.getChildren(); if (!newParentChildren.contains(this)) { newParentChildren.add(this); } } } /** * {@inheritDoc} */ @Override public void removeChild(@NotNull final MutableNode<String, V> node) { // Check sanity Validate.notNull(node, "node"); Node<String, V> toRemove = null; for (Node<String, V> current : getChildren()) { if (node.equals(current)) { toRemove = current; break; } } if (toRemove != null) { final MutableNode<String, V> mutableNodeToRemove = (MutableNode<String, V>) toRemove; synchronized (lock) { // TODO: Send tree changed event to parent? mutableNodeToRemove.clearParent(); getChildren().remove(mutableNodeToRemove); } } else { if (log.isWarnEnabled()) { log.warn("Node [" + node + "] was not a child of this: " + toString()); } } } /** * {@inheritDoc} */ @Override public void removeChildren(@NotNull final Filter<Node<String, V>> nodeFilter) { // Check sanity Validate.notNull(nodeFilter, "nodeFilter"); // Find all nodes which should be removed. List<MutableNode<String, V>> toRemove = new ArrayList<>(); for (Object current : ((AbstractStringKeyNode) this).getChildren()) { @SuppressWarnings("unchecked") final MutableNode<String, V> currentNode = (MutableNode<String, V>) current; if (nodeFilter.accept(currentNode)) { toRemove.add(currentNode); } } // Remove all appropriate nodes. if (toRemove.size() > 0) { synchronized (lock) { for (MutableNode<String, V> current : toRemove) { removeChild(current); } } } } /** * {@inheritDoc} */ @Override public void addChild(@NotNull final MutableNode<String, V> node) throws IllegalArgumentException { // Check sanity Validate.notNull(node, "node"); // Add only if the given node is not already present. if (!getChildren().contains(node)) { synchronized (lock) { node.setParent(this); } } } /** * {@inheritDoc} */ @Override public <X extends Node<String, V>> X getParent() { return (X) parent; } /** * {@inheritDoc} */ @Override public <X extends Path<String>> X getPath() { final List<String> pathSegments = new ArrayList<>(); for (MutableNode<String, V> current = this; current != null; current = current.getParent()) { pathSegments.add(current.getKey()); } Collections.reverse(pathSegments); // Create a Path from the reversed pathSegments List. Path<String> currentPath = null; for (String current : pathSegments) { currentPath = currentPath == null ? new StringPath(current) : currentPath.append(current); } // All done return (X) currentPath; } /** * The compareTo implementation of the AbstractStringKeyNode simply * compares the keys of then two nodes. * * @param that The non-null node sub-type to compare with. */ @Override public int compareTo(final Node<String, V> that) { // Check sanity as per the spec. Validate.notNull(that, "Cannot compare AbstractStringKeyNode to null."); if (this == that) { return 0; } // Delegate to Key comparison. final String thatKey = that.getKey() == null ? "" : that.getKey(); return getKey().compareTo(thatKey); } /** * The hashCode implementation of AbstractStringKeyNode uses the key, data and children as * basis for the calculation of resulting hashCode value. * {@inheritDoc} */ @Override public int hashCode() { final int dataHashCode = getData() == null ? 0 : getData().hashCode(); final int childHashCode = getChildren() == null ? 0 : getChildren().hashCode(); return key.hashCode() + dataHashCode + childHashCode; } /** * The equals implementation simply delegates to the hashCode to calculate the * {@inheritDoc} */ @Override public boolean equals(final Object obj) { // Check sanity if (!(obj instanceof AbstractStringKeyNode)) { return false; } else if (obj == this) { return true; } // Delegate, and return. return hashCode() == obj.hashCode(); } /** * @return A Debug string printout of this AbstractStringKeyNode. */ @Override public String toString() { final String suffix = getChildren().size() == 0 ? " [leaf node (0 children)]" : " [" + getChildren().size() + " children]"; return "AbstractStringKeyNode [" + key + "]: " + getData() + suffix; } /** * {@inheritDoc} */ @Override protected void validateEntityState() throws InternalStateValidationException { InternalStateValidationException.create() .notNullOrEmpty(key, "key") .endExpressionAndValidate(); } }