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

net.derquinse.common.collect.ImmutableHierarchy Maven / Gradle / Ivy

Go to download

Module containing support classes depending on Java SE 6, Guava 11 and Joda-Time 2.0

There is a newer version: 1.0.37
Show newest version
/*
 * 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 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 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 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 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 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 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