/* * Copyright 2015 Lukas Krejci * * 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.revapi.simple; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.revapi.Element; import org.revapi.query.DFSFilteringIterator; import org.revapi.query.Filter; import org.revapi.query.FilteringIterator; /** * A simple implementation of the {@link org.revapi.Element} interface intended to be extended. * * @author Lukas Krejci * @since 0.1 */ public abstract class SimpleElement implements Element { private Element parent; private SortedSet<Element> children; private static class EmptyIterator<E> implements Iterator<E> { @Override public boolean hasNext() { return false; } @Override public E next() { throw new NoSuchElementException(); } @Override public void remove() { throw new IllegalStateException(); } } private class ParentPreservingSet implements SortedSet<Element> { private final SortedSet<Element> set; private ParentPreservingSet(SortedSet<Element> set) { this.set = set; } @Override public boolean add(Element element) { boolean ret = set.add(element); if (ret) { element.setParent(SimpleElement.this); } return ret; } @Override public boolean addAll(@Nonnull Collection<? extends Element> c) { for (Element e : c) { add(e); } return !c.isEmpty(); } @Override public void clear() { for (Element e : this) { e.setParent(null); } set.clear(); } @Override public int size() { return set.size(); } @Override public boolean isEmpty() { return set.isEmpty(); } @Override public boolean contains(Object o) { return set.contains(o); } @Nonnull @Override public Iterator<Element> iterator() { return new ParentPreservingIterator(set.iterator()); } @Nonnull @Override public Object[] toArray() { return set.toArray(); } @Nonnull @Override public <T> T[] toArray(@Nonnull T[] a) { //noinspection SuspiciousToArrayCall return set.toArray(a); } @Override public boolean remove(Object o) { Iterator<Element> it = this.iterator(); while (it.hasNext()) { Element e = it.next(); if ((o == null && e == null) || (o != null && o.equals(e))) { it.remove(); if (e != null) { e.setParent(null); } return true; } } return false; } @Override public boolean containsAll(@Nonnull Collection<?> c) { return set.containsAll(c); } @Override public boolean removeAll(@Nonnull Collection<?> c) { boolean ret = false; for (Object o : c) { ret |= remove(o); } return ret; } @Override public boolean retainAll(@Nonnull Collection<?> c) { boolean ret = false; for (Object o : c) { if (!contains(o)) { ret = true; remove(o); } } return ret; } @Override public Comparator<? super Element> comparator() { return set.comparator(); } @Nonnull @Override public SortedSet<Element> subSet(Element fromElement, Element toElement) { return set.subSet(fromElement, toElement); } @Nonnull @Override public SortedSet<Element> headSet(Element toElement) { return set.headSet(toElement); } @Nonnull @Override public SortedSet<Element> tailSet(Element fromElement) { return set.tailSet(fromElement); } @Override public Element first() { return set.first(); } @Override public Element last() { return set.last(); } private class ParentPreservingIterator implements Iterator<Element> { private final Iterator<Element> it; Element last; private ParentPreservingIterator(Iterator<Element> it) { this.it = it; } @Override public boolean hasNext() { return it.hasNext(); } @Override public Element next() { last = it.next(); return last; } @Override public void remove() { if (last != null) { last.setParent(null); } it.remove(); } } } /** * This default implementation uses the {@link #newChildrenInstance()} to initialize the children set and wraps * it in a private set implementation that automagically changes the parent of the elements based on the * membership. * * @return children of this element */ @Nonnull public SortedSet<? extends Element> getChildren() { if (children == null) { children = new ParentPreservingSet(newChildrenInstance()); } return children; } /** * Override this method if you need some specialized instance of sorted set or want to do some custom pre-populating * or initialization of the children. This default implementation merely returns an empty new * {@link java.util.TreeSet} instance. * * @return a new sorted set instance to store the children in */ @Nonnull protected SortedSet<Element> newChildrenInstance() { return new TreeSet<>(); } /** * @return The parent element of this element. */ @Override @Nullable public Element getParent() { return parent; } /** * Sets the parent element. No other processing is automagically done (i.e. the parent's children set is <b>NOT</b> * updated by calling this method). * * @param parent the new parent element */ @Override public void setParent(@Nullable Element parent) { this.parent = parent; } @Override @Nonnull public final <T extends Element> List<T> searchChildren(@Nonnull Class<T> resultType, boolean recurse, @Nullable Filter<? super T> filter) { List<T> results = new ArrayList<>(); searchChildren(results, resultType, recurse, filter); return results; } @Override public final <T extends Element> void searchChildren(@Nonnull List<T> results, @Nonnull Class<T> resultType, boolean recurse, @Nullable Filter<? super T> filter) { for (Element e : getChildren()) { if (resultType.isAssignableFrom(e.getClass())) { T te = resultType.cast(e); if (filter == null || filter.applies(te)) { results.add(te); } } if (recurse && (filter == null || filter.shouldDescendInto(e))) { e.searchChildren(results, resultType, true, filter); } } } /** * This default implementation assumes that {@code toString()} can do the job. * * @return the human readable representation of this element * * @see org.revapi.Element#getFullHumanReadableString() */ @Override @Nonnull public String getFullHumanReadableString() { return toString(); } @Override @SuppressWarnings("unchecked") @Nonnull public <T extends Element> Iterator<T> iterateOverChildren(@Nonnull Class<T> resultType, boolean recurse, @Nullable Filter<? super T> filter) { if (children == null) { return new EmptyIterator<>(); } return recurse ? new DFSFilteringIterator<>(getChildren().iterator(), resultType, filter) : new FilteringIterator<>((Iterator<T>) getChildren().iterator(), resultType, filter); } @Nonnull protected <T extends Element> List<T> getDirectChildrenOfType(@Nonnull Class<T> type) { return searchChildren(type, false, null); } }