/*
* Copyright 2009 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 org.gradle.api.internal;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.DomainObjectCollection;
import org.gradle.api.internal.collections.BroadcastingCollectionEventRegister;
import org.gradle.api.internal.collections.CollectionEventRegister;
import org.gradle.api.internal.collections.CollectionFilter;
import org.gradle.api.internal.collections.FilteredCollection;
import org.gradle.api.specs.Spec;
import org.gradle.api.specs.Specs;
import org.gradle.internal.ImmutableActionSet;
import org.gradle.util.ConfigureUtil;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class DefaultDomainObjectCollection<T> extends AbstractCollection<T> implements DomainObjectCollection<T>, WithEstimatedSize {
private final Class<? extends T> type;
private final CollectionEventRegister<T> eventRegister;
private final Collection<T> store;
private final boolean hasConstantTimeSizeMethod;
private ImmutableActionSet<Void> mutateAction = ImmutableActionSet.empty();
public DefaultDomainObjectCollection(Class<? extends T> type, Collection<T> store) {
this(type, store, new BroadcastingCollectionEventRegister<T>());
}
protected DefaultDomainObjectCollection(Class<? extends T> type, Collection<T> store, CollectionEventRegister<T> eventRegister) {
this.type = type;
this.store = store;
this.eventRegister = eventRegister;
this.hasConstantTimeSizeMethod = Estimates.isKnownToHaveConstantTimeSizeMethod(store);
}
protected DefaultDomainObjectCollection(DefaultDomainObjectCollection<? super T> collection, CollectionFilter<T> filter) {
this(filter.getType(), collection.filteredStore(filter), collection.filteredEvents(filter));
}
public Class<? extends T> getType() {
return type;
}
protected Collection<T> getStore() {
return store;
}
protected CollectionEventRegister<T> getEventRegister() {
return eventRegister;
}
protected CollectionFilter<T> createFilter(Spec<? super T> filter) {
return createFilter(getType(), filter);
}
protected <S extends T> CollectionFilter<S> createFilter(Class<S> type) {
return new CollectionFilter<S>(type);
}
protected <S extends T> CollectionFilter<S> createFilter(Class<? extends S> type, Spec<? super S> spec) {
return new CollectionFilter<S>(type, spec);
}
protected <S extends T> DefaultDomainObjectCollection<S> filtered(CollectionFilter<S> filter) {
return new DefaultDomainObjectCollection<S>(this, filter);
}
protected <S extends T> Collection<S> filteredStore(CollectionFilter<S> filter) {
return new FilteredCollection<T, S>(this, filter);
}
protected <S extends T> CollectionEventRegister<S> filteredEvents(CollectionFilter<S> filter) {
return getEventRegister().filtered(filter);
}
public DomainObjectCollection<T> matching(final Spec<? super T> spec) {
return filtered(createFilter(spec));
}
public DomainObjectCollection<T> matching(Closure spec) {
return matching(Specs.<T>convertClosureToSpec(spec));
}
public <S extends T> DomainObjectCollection<S> withType(final Class<S> type) {
return filtered(createFilter(type));
}
public Iterator<T> iterator() {
if (constantTimeIsEmpty()) {
return Iterators.emptyIterator();
}
return new IteratorImpl(getStore().iterator());
}
public void all(Action<? super T> action) {
action = whenObjectAdded(action);
if (constantTimeIsEmpty()) {
return;
}
// copy in case any actions mutate the store
// linked list because the underlying store may preserve order
// We make best effort not to create an intermediate collection if this container
// is empty.
Collection<T> copied = null;
for (T t : this) {
if (copied == null) {
copied = Lists.newArrayListWithExpectedSize(estimatedSize());
}
copied.add(t);
}
if (copied != null) {
for (T t : copied) {
action.execute(t);
}
}
}
/**
* Returns true if, and only if, the store is empty AND we know that we
* can query its size in constant time. Otherwise it returns false, which means
* that the collection may contain elements or may be empty (we don't know without
* spending too much time).
*
* @return true if and only if the store is empty and can tell in constant time
*/
private boolean constantTimeIsEmpty() {
return hasConstantTimeSizeMethod && store.isEmpty();
}
public void all(Closure action) {
all(toAction(action));
}
public <S extends T> DomainObjectCollection<S> withType(Class<S> type, Action<? super S> configureAction) {
DomainObjectCollection<S> result = withType(type);
result.all(configureAction);
return result;
}
public <S extends T> DomainObjectCollection<S> withType(Class<S> type, Closure configureClosure) {
DomainObjectCollection<S> result = withType(type);
result.all(configureClosure);
return result;
}
public Action<? super T> whenObjectAdded(Action<? super T> action) {
return eventRegister.registerAddAction(action);
}
public Action<? super T> whenObjectRemoved(Action<? super T> action) {
return eventRegister.registerRemoveAction(action);
}
public void whenObjectAdded(Closure action) {
whenObjectAdded(toAction(action));
}
public void whenObjectRemoved(Closure action) {
whenObjectRemoved(toAction(action));
}
/**
* Adds an action which is executed before this collection is mutated. Any exception thrown by the action will veto the mutation.
*/
public void beforeChange(Action<Void> action) {
mutateAction = mutateAction.add(action);
}
private Action<? super T> toAction(Closure action) {
return ConfigureUtil.configureUsing(action);
}
public boolean add(T toAdd) {
assertMutable();
return doAdd(toAdd);
}
private boolean doAdd(T toAdd) {
if (getStore().add(toAdd)) {
didAdd(toAdd);
eventRegister.getAddAction().execute(toAdd);
return true;
} else {
return false;
}
}
protected void didAdd(T toAdd) {
}
public boolean addAll(Collection<? extends T> c) {
assertMutable();
boolean changed = false;
for (T o : c) {
if (doAdd(o)) {
changed = true;
}
}
return changed;
}
public void clear() {
assertMutable();
if (constantTimeIsEmpty()) {
return;
}
Object[] c = toArray();
getStore().clear();
for (Object o : c) {
eventRegister.getRemoveAction().execute((T) o);
}
}
public boolean contains(Object o) {
return getStore().contains(o);
}
public boolean containsAll(Collection<?> c) {
return getStore().containsAll(c);
}
public boolean isEmpty() {
return getStore().isEmpty();
}
public boolean remove(Object o) {
assertMutable();
return doRemove(o);
}
private boolean doRemove(Object o) {
if (getStore().remove(o)) {
@SuppressWarnings("unchecked") T cast = (T) o;
didRemove(cast);
eventRegister.getRemoveAction().execute(cast);
return true;
} else {
return false;
}
}
protected void didRemove(T t) {
}
public boolean removeAll(Collection<?> c) {
assertMutable();
if (constantTimeIsEmpty()) {
return false;
}
boolean changed = false;
for (Object o : c) {
if (doRemove(o)) {
changed = true;
}
}
return changed;
}
public boolean retainAll(Collection<?> target) {
assertMutable();
Object[] existingItems = toArray();
boolean changed = false;
for (Object existingItem : existingItems) {
if (!target.contains(existingItem)) {
doRemove(existingItem);
changed = true;
}
}
return changed;
}
public int size() {
return getStore().size();
}
@Override
public int estimatedSize() {
return Estimates.estimateSizeOf(getStore());
}
public Collection<T> findAll(Closure cl) {
return findAll(cl, new ArrayList<T>());
}
protected <S extends Collection<? super T>> S findAll(Closure cl, S matches) {
if (constantTimeIsEmpty()) {
return matches;
}
for (T t : filteredStore(createFilter(Specs.<Object>convertClosureToSpec(cl)))) {
matches.add(t);
}
return matches;
}
protected void assertMutable() {
mutateAction.execute(null);
}
protected class IteratorImpl implements Iterator<T>, WithEstimatedSize {
private final Iterator<T> iterator;
private T currentElement;
public IteratorImpl(Iterator<T> iterator) {
this.iterator = iterator;
}
public boolean hasNext() {
return iterator.hasNext();
}
public T next() {
currentElement = iterator.next();
return currentElement;
}
public void remove() {
assertMutable();
iterator.remove();
didRemove(currentElement);
getEventRegister().getRemoveAction().execute(currentElement);
currentElement = null;
}
@Override
public int estimatedSize() {
return DefaultDomainObjectCollection.this.estimatedSize();
}
}
}