net.derquinse.common.collect.ImmutableHierarchy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of derquinse-common-base Show documentation
Show all versions of derquinse-common-base Show documentation
Module containing support classes depending on Java SE 6, Guava 11 and Joda-Time 2.0
/*
* Copyright (C) the original author or authors.
*
* 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 net.derquinse.common.collect;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* An immutable and thread-safe implementation of hierarchy.
* @author Andres Rodriguez
* @param Type of the elements in the hierarchy.
*/
public abstract class ImmutableHierarchy extends AbstractHierarchy {
/**
* Returns an empty immutable hierarchy.
*/
@SuppressWarnings("unchecked")
public static ImmutableHierarchy of() {
return (ImmutableHierarchy) EmptyImmutableHierarchy.INSTANCE;
}
/**
* Returns an immutable copy of the provided hierarchy. If the argument is itself an immutable
* hierarchy no copy is performed.
* @param hierarchy Hierarchy to copy.
* @return An immutable hierarchy equal to the the one provided.
*/
public static ImmutableHierarchy copyOf(Hierarchy extends E> hierarchy) {
checkNotNull(hierarchy, "The source hierachy must be provided");
if (hierarchy instanceof ImmutableHierarchy>) {
@SuppressWarnings("unchecked")
final ImmutableHierarchy h = (ImmutableHierarchy) hierarchy;
return h;
} else if (hierarchy.isEmpty()) {
return of();
}
final Builder builder = builder();
return builder.addHierarchy(null, hierarchy, null, true).build();
}
/**
* Returns a new builder that does not allow out of order insertion (initially).
*/
public static Builder builder() {
return new Builder();
}
/**
* Returns a new builder.
* @param outOfOrder Whether to allow out of order insertion.
*/
public static Builder builder(boolean outOfOrder) {
return new Builder().setAllowOutOfOrder(outOfOrder);
}
/** Default constructor. */
ImmutableHierarchy() {
}
/*
* (non-Javadoc)
* @see net.derquinse.common.collect.Hierarchy#elementSet()()
*/
public abstract ImmutableSet elementSet();
/*
* (non-Javadoc)
* @see net.derquinse.common.collect.Hierarchy#getDescendants(java.lang.Object)
*/
@Override
public final ImmutableSet getDescendants(@Nullable E element) {
if (element == null) {
return elementSet();
}
return getMemberDescendants(element);
}
/** Returns the descendants of an existing element. */
abstract ImmutableSet getMemberDescendants(E element);
/**
* Builder for immutable hierarchies.
* @author Andres Rodriguez
* @param Type of the elements.
*/
public static final class Builder implements net.derquinse.common.base.Builder> {
private final Set elements = Sets.newHashSet();
private final Set unaddedParents = Sets.newHashSet();
private final List firstLevel = Lists.newLinkedList();
private final Map parents = Maps.newHashMap();
private final ListMultimap children = LinkedListMultimap.create();
private boolean allowOutOfOrder;
/** Constructor. Use static factory method. */
private Builder(boolean allowOutOfOrder) {
this.allowOutOfOrder = allowOutOfOrder;
}
private Builder() {
this(false);
}
private void checkReady() {
checkState(unaddedParents.isEmpty(), "There are referenced parents that have not been added yet.");
}
Set getElements() {
checkReady();
return elements;
}
List getFirstLevel() {
checkReady();
return firstLevel;
}
Map getParents() {
return parents;
}
ListMultimap getChildren() {
checkReady();
return children;
}
public boolean isAllowOutOfOrder() {
return allowOutOfOrder;
}
public Builder setAllowOutOfOrder(boolean allowOutOfOrder) {
this.allowOutOfOrder = allowOutOfOrder;
return this;
}
public Builder add(E parent, E element) {
checkArgument(!elements.contains(element), "Duplicate entries not allowed in a hierarchy");
if (parent == null) {
firstLevel.add(element);
} else {
if (!allowOutOfOrder) {
checkArgument(elements.contains(parent), "Parent not found in the hierarchy");
}
E p = parent;
@SuppressWarnings("unchecked")
final Set visited = Sets.newHashSet(element);
while (p != null) {
checkState(visited.add(p), "Loop detected: element [%s] visited twice", p);
p = parents.get(p);
}
children.put(parent, element);
parents.put(element, parent);
if (!elements.contains(parent)) {
unaddedParents.add(parent);
}
}
unaddedParents.remove(element);
elements.add(element);
return this;
}
public Builder addAll(E parent, Iterable extends E> elements) {
for (E element : elements) {
add(parent, element);
}
return this;
}
public Builder addAll(E parent, E... elements) {
for (E element : elements) {
add(parent, element);
}
return this;
}
private static Hierarchy check(Hierarchy extends T> hierarchy) {
checkNotNull(hierarchy, "The source hierarchy is required");
// Safe because we are just reading.
@SuppressWarnings("unchecked")
Hierarchy h = (Hierarchy) hierarchy;
return h;
}
public Builder addHierarchy(E parent, Hierarchy extends E> hierarchy, @Nullable E root, boolean includeRoot) {
final Hierarchy h = check(hierarchy);
checkArgument(root == null || hierarchy.elementSet().contains(root));
List level;
if (root != null) {
if (includeRoot) {
add(parent, root);
parent = root;
}
level = h.getChildren(root);
} else {
level = h.getFirstLevel();
}
addHierarchyRec(parent, h, level);
return this;
}
private void addHierarchyRec(E parent, Hierarchy hierarchy, List level) {
for (E element : level) {
add(parent, element);
addHierarchyRec(element, hierarchy, hierarchy.getChildren(element));
}
}
public Builder addHierarchy(E parent, Hierarchy hierarchy, @Nullable F root, boolean includeRoot,
Function super F, E> function) {
final Hierarchy h = check(hierarchy);
checkNotNull(hierarchy, "The transformation function is required");
checkArgument(root == null || hierarchy.elementSet().contains(root));
List level;
if (root != null) {
if (includeRoot) {
E newRoot = function.apply(root);
add(parent, newRoot);
parent = newRoot;
}
level = h.getChildren(root);
} else {
level = h.getFirstLevel();
}
addHierarchyRec(parent, h, level, function);
return this;
}
private void addHierarchyRec(E parent, Hierarchy hierarchy, List level, Function super F, E> function) {
for (F element : level) {
E transformed = function.apply(element);
add(parent, transformed);
addHierarchyRec(transformed, hierarchy, hierarchy.getChildren(element), function);
}
}
/**
* Builds and returns an immutable hierarchy with the nodes added up to the method call.
* @return An immutable hierarchy.
* @throws IllegalStateException if there are referenced parents that are not part of the
* hierarchy yet.
*/
@Override
public ImmutableHierarchy build() {
checkReady();
if (elements.isEmpty()) {
return of();
} else if (children.isEmpty()) {
return new FlatImmutableHierarchy(firstLevel);
}
return new RegularImmutableHierarchy(this);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy