/* * Copyright 2015-2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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. */ package org.hawkular.inventory.api.model; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.function.BiFunction; import org.hawkular.inventory.api.TreeTraversal; import org.hawkular.inventory.paths.Path; import org.hawkular.inventory.paths.RelativePath; /** * An abstract base class for the various hash trees. This is not instantiable/subclassable by the users on purpose * because only concrete subclasses defined in this package make sense for use. Nevertheless this class is public to * enable abstracting over the different subclasses. * * @author Lukas Krejci * @since 0.18.0 */ public abstract class AbstractHashTree<This extends AbstractHashTree<This, H>, H extends Serializable> implements Serializable { protected final RelativePath path; protected final H hash; protected final Map<Path.Segment, This> children; //jackson support AbstractHashTree() { this(null, null, null); } AbstractHashTree(RelativePath path, H hash, Map<Path.Segment, This> children) { this.path = path; this.hash = hash; this.children = children; } public Collection<This> getChildren() { return children.values(); } public This getChild(Path.Segment path) { return children.get(path); } public H getHash() { return hash; } public RelativePath getPath() { return path; } public TreeTraversal<This> traversal() { return new TreeTraversal<>(t -> t.children.values().iterator()); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AbstractHashTree<?, ?> tree = (AbstractHashTree<?, ?>) o; return hash.equals(tree.hash); } @Override public int hashCode() { return hash.hashCode(); } @Override public String toString() { return "Tree[" + "path=" + path + ", hash='" + hash + '\'' + ",children=" + children.values() + ']'; } interface TreeConstructor<T extends AbstractHashTree<T, H>, H extends Serializable> { T construct(RelativePath path, H hash, Map<Path.Segment, T> children); } /** * An interface that is implemented by all tree hash builders. This is only made public for the cases where the * users would like to abstract over different tree implementations. * * @param <This> the type of the concrete implementation * @param <Child> the type of the tree child builder * @param <Tree> the type of the tree being built * @param <Hash> the type of the hash used in the tree */ public interface Builder<This extends Builder<This, Child, Tree, Hash>, Child extends ChildBuilder<Child, This, ?, Tree, Hash>, Tree extends AbstractHashTree<Tree, Hash>, Hash extends Serializable> { This withPath(RelativePath path); This withHash(Hash hash); Hash getHash(); RelativePath getPath(); boolean hasChildren(); Child startChild(); void addChild(Tree childTree); } /** * Interface for top-level hash tree builders. I.e. only these actually have the build method that is used to * produce the final tree. * * @param <This> the type of the concrete implementation * @param <Child> the type of the tree child builder * @param <Tree> the type of the tree being built * @param <Hash> the type of the hash used in the tree */ public interface TopBuilder<This extends TopBuilder<This, Child, Tree, Hash>, Child extends ChildBuilder<Child, This, ?, Tree, Hash>, Tree extends AbstractHashTree<Tree, Hash>, Hash extends Serializable> extends Builder<This, Child, Tree, Hash> { Tree build(); } /** * A builder used to build sub trees of some parent tree node. * * @param <This> the type of the concrete implementation of this interface * @param <Parent> the type of the builder of the parent tree node * @param <Child> the type of the builder used to build the subtrees of the tree node built by this builder * @param <Tree> the type of the tree being built * @param <Hash> the type of the hash used in the tree */ public interface ChildBuilder<This extends ChildBuilder<This, Parent, Child, Tree, Hash>, Parent extends Builder<?, This, Tree, Hash>, Child extends ChildBuilder<Child, This, ?, Tree, Hash>, Tree extends AbstractHashTree<Tree, Hash>, Hash extends Serializable> extends Builder<This, Child, Tree, Hash> { Parent getParent(); Parent endChild(); } static class AbstractBuilder<This extends Builder<This, Child, T, H>, Child extends ChildBuilder<Child, This, ?, T, H>, T extends AbstractHashTree<T, H>, H extends Serializable> implements Builder<This, Child, T, H> { private final TreeConstructor<T, H> tctor; private final BiFunction<TreeConstructor<T, H>, This, Child> cctor; private RelativePath path; private H hash; private Map<Path.Segment, T> children; AbstractBuilder(TreeConstructor<T, H> tctor, BiFunction<TreeConstructor<T, H>, This, Child> cctor) { this.tctor = tctor; this.cctor = cctor; } public This withPath(RelativePath path) { this.path = path; return castThis(); } public This withHash(H hash) { this.hash = hash; return castThis(); } public H getHash() { return hash; } public RelativePath getPath() { return path; } public boolean hasChildren() { return children != null && !children.isEmpty(); } public void addChild(T childTree) { getChildren().put(childTree.getPath().getSegment(), childTree); } public Child startChild() { return cctor.apply(tctor, castThis()); } protected T build() { if ((path == null || hash == null) && (children != null && !children.isEmpty())) { throw new IllegalStateException("Cannot construct a tree hash node without a path or " + "hash and with children. While empty tree without a hash or path is OK, having children " + "assumes the parent to be fully established."); } if (path != null) { int myDepth = path.getDepth(); for (T child : getChildren().values()) { int childDepth = child.getPath().getDepth(); if (!path.isParentOf(child.getPath()) || childDepth != myDepth + 1) { throw new IllegalStateException( "When building a tree node with path " + path + " an attempt " + "to add a child on path " + child.getPath() + " was made. The child's path must extend the parent's path by exactly" + " one segment, which is not true in this case."); } } } return tctor.construct(path, hash, Collections.unmodifiableMap(getChildren())); } private Map<Path.Segment, T> getChildren() { if (children == null) { children = new HashMap<>(); } return children; } @SuppressWarnings("unchecked") private This castThis() { return (This) this; } } abstract static class AbstractChildBuilder< This extends AbstractChildBuilder<This, Parent, Child, T, H>, Parent extends Builder<?, This, T, H>, Child extends AbstractChildBuilder<Child, This, ?, T, H>, T extends AbstractHashTree<T, H>, H extends Serializable> extends AbstractBuilder<This, Child, T, H> implements ChildBuilder<This, Parent, Child, T, H> { private final Parent parent; AbstractChildBuilder(TreeConstructor<T, H> tctor, Parent parent, BiFunction<TreeConstructor<T, H>, This, Child> cctor) { super(tctor, cctor); this.parent = parent; } public Parent getParent() { return parent; } @SuppressWarnings("unchecked") public Parent endChild() { T tree = build(); parent.addChild(tree); return parent; } } }