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

org.gradle.api.internal.DefaultNamedDomainObjectCollection Maven / Gradle / Ivy

There is a newer version: 8.6
Show newest version
/*
 * Copyright 2010 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.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Named;
import org.gradle.api.NamedDomainObjectCollection;
import org.gradle.api.NamedDomainObjectCollectionSchema;
import org.gradle.api.NamedDomainObjectProvider;
import org.gradle.api.Namer;
import org.gradle.api.Rule;
import org.gradle.api.UnknownDomainObjectException;
import org.gradle.api.internal.collections.CollectionEventRegister;
import org.gradle.api.internal.collections.CollectionFilter;
import org.gradle.api.internal.collections.ElementSource;
import org.gradle.api.internal.plugins.DslObject;
import org.gradle.api.internal.provider.AbstractReadOnlyProvider;
import org.gradle.api.internal.provider.ProviderInternal;
import org.gradle.api.internal.provider.Providers;
import org.gradle.api.provider.Provider;
import org.gradle.api.reflect.TypeOf;
import org.gradle.api.specs.Spec;
import org.gradle.api.specs.Specs;
import org.gradle.internal.Cast;
import org.gradle.internal.ImmutableActionSet;
import org.gradle.internal.metaobject.AbstractDynamicObject;
import org.gradle.internal.metaobject.DynamicInvokeResult;
import org.gradle.internal.metaobject.DynamicObject;
import org.gradle.internal.metaobject.MethodAccess;
import org.gradle.internal.metaobject.MethodMixIn;
import org.gradle.internal.metaobject.PropertyAccess;
import org.gradle.internal.metaobject.PropertyMixIn;
import org.gradle.internal.reflect.Instantiator;
import org.gradle.util.ConfigureUtil;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

public class DefaultNamedDomainObjectCollection extends DefaultDomainObjectCollection implements NamedDomainObjectCollection, MethodMixIn, PropertyMixIn {

    private final Instantiator instantiator;
    private final Namer namer;
    protected final Index index;

    private final ContainerElementsDynamicObject elementsDynamicObject = new ContainerElementsDynamicObject();

    private final List rules = new ArrayList();
    private final Set applyingRulesFor = new HashSet();
    private ImmutableActionSet> whenKnown = ImmutableActionSet.empty();

    public DefaultNamedDomainObjectCollection(Class type, ElementSource store, Instantiator instantiator, Namer namer, CollectionCallbackActionDecorator callbackActionDecorator) {
        super(type, store, callbackActionDecorator);
        this.instantiator = instantiator;
        this.namer = namer;
        this.index = new UnfilteredIndex();
        index();
    }

    protected void index() {
        for (T t : getStore()) {
            index.put(namer.determineName(t), t);
        }
    }

    protected DefaultNamedDomainObjectCollection(Class type, ElementSource store, CollectionEventRegister eventRegister, Index index, Instantiator instantiator, Namer namer) {
        super(type, store, eventRegister);
        this.instantiator = instantiator;
        this.namer = namer;
        this.index = index;
    }

    // should be protected, but use of the class generator forces it to be public
    public DefaultNamedDomainObjectCollection(DefaultNamedDomainObjectCollection collection, CollectionFilter filter, Instantiator instantiator, Namer namer) {
        this(filter.getType(), collection.filteredStore(filter), collection.filteredEvents(filter), collection.filteredIndex(filter), instantiator, namer);
    }

    @Override
    protected  boolean doAdd(I toAdd, Action notification) {
        final String name = namer.determineName(toAdd);
        if (index.get(name) == null) {
            boolean added = super.doAdd(toAdd, notification);
            if (added) {
                whenKnown.execute(new ObjectBackedElementInfo(name, toAdd));
            }
            return added;
        } else {
            handleAttemptToAddItemWithNonUniqueName(toAdd);
            return false;
        }
    }

    @Override
    protected void realized(ProviderInternal provider) {
        super.realized(provider);
        index.removePending(provider);
    }

    @Override
    public boolean addAll(Collection c) {
        assertMutable("addAll(Collection)");
        boolean changed = super.addAll(c);
        if (changed) {
            for (T t : c) {
                String name = namer.determineName(t);
                whenKnown.execute(new ObjectBackedElementInfo(name, t));
            }
        }
        return changed;
    }

    @Override
    public void addLater(final Provider provider) {
        assertMutable("addLater(Provider)");
        super.addLater(provider);
        if (provider instanceof Named) {
            final Named named = (Named) provider;
            index.putPending(named.getName(), Providers.internal(provider));
            deferredElementKnown(named.getName(), provider);
        }
    }

    public void whenElementKnown(Action> action) {
        whenKnown = whenKnown.add(action);
        Iterator iterator = iteratorNoFlush();
        while (iterator.hasNext()) {
            T next = iterator.next();
            whenKnown.execute(new ObjectBackedElementInfo(namer.determineName(next), next));
        }

        for (Map.Entry> entry : index.getPendingAsMap().entrySet()) {
            deferredElementKnown(entry.getKey(), entry.getValue());
        }
    }

    protected final void deferredElementKnown(String name, Provider provider) {
        whenKnown.execute(new ProviderBackedElementInfo(name, Providers.internal(provider)));
    }

    @Override
    protected void didAdd(T toAdd) {
        index.put(namer.determineName(toAdd), toAdd);
    }

    @Override
    public void clear() {
        super.clear();
        index.clear();
    }

    @Override
    protected void didRemove(T t) {
        index.remove(namer.determineName(t));
    }

    @Override
    protected void didRemove(ProviderInternal t) {
        if (t instanceof Named) {
            index.removePending(((Named) t).getName());
        }
        if (t instanceof AbstractDomainObjectCreatingProvider) {
            ((AbstractDomainObjectCreatingProvider) t).removedBeforeRealized = true;
        }
    }

    /**
     * 

Subclass hook for implementations wanting to throw an exception when an attempt is made to add an item with the same name as an existing item.

* *

This implementation does not thrown an exception, meaning that {@code add(T)} will simply return {@code false}. * * @param o The item that is being attempted to add. */ protected void handleAttemptToAddItemWithNonUniqueName(T o) { // do nothing } /** * Asserts that an item with the given name can be added to this collection. */ protected void assertCanAdd(String name) { if (hasWithName(name)) { throw new InvalidUserDataException(String.format("Cannot add a %s with name '%s' as a %s with that name already exists.", getTypeDisplayName(), name, getTypeDisplayName())); } } /** * Asserts that the given item can be added to this collection. */ protected void assertCanAdd(T t) { assertCanAdd(getNamer().determineName(t)); } @Override public Namer getNamer() { return (Namer) this.namer; } protected Instantiator getInstantiator() { return instantiator; } protected Index filteredIndex(CollectionFilter filter) { return index.filter(filter); } /** * Creates a filtered version of this collection. */ @Override protected DefaultNamedDomainObjectCollection filtered(CollectionFilter filter) { return instantiator.newInstance(DefaultNamedDomainObjectCollection.class, this, filter, instantiator, namer); } public String getDisplayName() { return getTypeDisplayName() + " container"; } @Override public String toString() { return getDisplayName(); } @Override public SortedMap getAsMap() { return index.asMap(); } @Override public SortedSet getNames() { NavigableSet realizedNames = index.asMap().navigableKeySet(); Set pendingNames = index.getPendingAsMap().keySet(); if (pendingNames.isEmpty()) { return realizedNames; } TreeSet allNames = new TreeSet(realizedNames); allNames.addAll(pendingNames); return allNames; } @Override public NamedDomainObjectCollection withType(Class type) { return filtered(createFilter(type)); } @Override public NamedDomainObjectCollection matching(Spec spec) { return filtered(createFilter(spec)); } @Override public NamedDomainObjectCollection matching(Closure spec) { return matching(Specs.convertClosureToSpec(spec)); } @Override public T findByName(String name) { T value = findByNameWithoutRules(name); if (value != null) { return value; } ProviderInternal provider = index.getPending(name); if (provider != null) { // TODO - this isn't correct, assumes that a side effect is to add the element provider.getOrNull(); // Use the index here so we can apply any filters to the realized element return index.get(name); } if (!applyRules(name)) { return null; } return findByNameWithoutRules(name); } protected boolean hasWithName(String name) { return index.get(name) != null || index.getPending(name) != null; } @Nullable protected T findByNameWithoutRules(String name) { return index.get(name); } @Nullable protected ProviderInternal findByNameLaterWithoutRules(String name) { return index.getPending(name); } protected T removeByName(String name) { T it = getByName(name); if (remove(it)) { return it; } else { // unclear what the best thing to do here would be throw new IllegalStateException(String.format("found '%s' with name '%s' but remove() returned false", it, name)); } } @Override public T getByName(String name) throws UnknownDomainObjectException { T t = findByName(name); if (t == null) { throw createNotFoundException(name); } return t; } @Override public T getByName(String name, Closure configureClosure) throws UnknownDomainObjectException { return getByName(name, ConfigureUtil.configureUsing(configureClosure)); } @Override public T getByName(String name, Action configureAction) throws UnknownDomainObjectException { assertMutable("getByName(String, Action)"); T t = getByName(name); configureAction.execute(t); return t; } @Override public T getAt(String name) throws UnknownDomainObjectException { return getByName(name); } @Override public NamedDomainObjectProvider named(String name) throws UnknownDomainObjectException { NamedDomainObjectProvider provider = findDomainObject(name); if (provider == null) { throw createNotFoundException(name); } return Cast.uncheckedCast(provider); } @Override public NamedDomainObjectProvider named(String name, Action configurationAction) throws UnknownDomainObjectException { assertMutable("named(String, Action)"); NamedDomainObjectProvider provider = named(name); provider.configure(configurationAction); return provider; } @Override public NamedDomainObjectProvider named(String name, Class type) throws UnknownDomainObjectException { AbstractNamedDomainObjectProvider provider = Cast.uncheckedCast(named(name)); Class actual = provider.type; if (!type.isAssignableFrom(actual)) { throw createWrongTypeException(name, type, actual); } return provider; } protected InvalidUserDataException createWrongTypeException(String name, Class expected, Class actual) { return new InvalidUserDataException(String.format("The domain object '%s' (%s) is not a subclass of the given type (%s).", name, actual.getCanonicalName(), expected.getCanonicalName())); } @Override public NamedDomainObjectProvider named(String name, Class type, Action configurationAction) throws UnknownDomainObjectException { assertMutable("named(String, Class, Action)"); NamedDomainObjectProvider provider = named(name, type); provider.configure(configurationAction); return provider; } @Override public MethodAccess getAdditionalMethods() { return getElementsAsDynamicObject(); } @Override public PropertyAccess getAdditionalProperties() { return getElementsAsDynamicObject(); } protected DynamicObject getElementsAsDynamicObject() { return elementsDynamicObject; } @Override public NamedDomainObjectCollectionSchema getCollectionSchema() { return new NamedDomainObjectCollectionSchema() { @Override public Iterable getElements() { // Simple scheme is to just present the public type of the container return Iterables.transform(getNames(), new Function() { @Override public NamedDomainObjectSchema apply(final String name) { return new NamedDomainObjectSchema() { @Override public String getName() { return name; } @Override public TypeOf getPublicType() { return TypeOf.typeOf(getType()); } }; } }); } }; } /** * @return true if the method _may_ have done some work */ private boolean applyRules(String name) { if (rules.isEmpty() || applyingRulesFor.contains(name)) { return false; } applyingRulesFor.add(name); try { for (Rule rule : rules) { rule.apply(name); } } finally { applyingRulesFor.remove(name); } return true; } @Override public Rule addRule(Rule rule) { rules.add(rule); return rule; } @Override public Rule addRule(final String description, final Closure ruleAction) { return addRule(new RuleAdapter(description) { @Override public void apply(String domainObjectName) { ruleAction.call(domainObjectName); } }); } @Override public Rule addRule(final String description, final Action ruleAction) { return addRule(new RuleAdapter(description) { @Override public void apply(String domainObjectName) { ruleAction.execute(domainObjectName); } }); } private static abstract class RuleAdapter implements Rule { private final String description; RuleAdapter(String description) { this.description = description; } @Override public String getDescription() { return description; } @Override public String toString() { return "Rule: " + description; } } @Override public List getRules() { return Collections.unmodifiableList(rules); } protected UnknownDomainObjectException createNotFoundException(String name) { return new UnknownDomainObjectException(String.format("%s with name '%s' not found.", getTypeDisplayName(), name)); } protected String getTypeDisplayName() { return getType().getSimpleName(); } private class ContainerElementsDynamicObject extends AbstractDynamicObject { @Override public String getDisplayName() { return DefaultNamedDomainObjectCollection.this.getDisplayName(); } @Override public boolean hasProperty(String name) { return findByName(name) != null; } @Override public DynamicInvokeResult tryGetProperty(String name) { T t = findByName(name); return t == null ? DynamicInvokeResult.notFound() : DynamicInvokeResult.found(t); } @Override public Map getProperties() { return getAsMap(); } @Override public boolean hasMethod(String name, Object... arguments) { return isConfigureMethod(name, arguments); } @Override public DynamicInvokeResult tryInvokeMethod(String name, Object... arguments) { if (isConfigureMethod(name, arguments)) { return DynamicInvokeResult.found(ConfigureUtil.configure((Closure) arguments[0], getByName(name))); } return DynamicInvokeResult.notFound(); } private boolean isConfigureMethod(String name, Object... arguments) { return (arguments.length == 1 && arguments[0] instanceof Closure) && hasProperty(name); } } protected interface Index { void put(String name, T value); @Nullable T get(String name); void remove(String name); void clear(); NavigableMap asMap(); Index filter(CollectionFilter filter); @Nullable ProviderInternal getPending(String name); void putPending(String name, ProviderInternal provider); void removePending(String name); void removePending(ProviderInternal provider); Map> getPendingAsMap(); } protected static class UnfilteredIndex implements Index { private final Map> pendingMap = Maps.newLinkedHashMap(); private final NavigableMap map = new TreeMap(); @Override public NavigableMap asMap() { return map; } @Override public void put(String name, T value) { map.put(name, value); } @Override public T get(String name) { return map.get(name); } @Override public void remove(String name) { map.remove(name); } @Override public void clear() { map.clear(); pendingMap.clear(); } @Override public Index filter(CollectionFilter filter) { return new FilteredIndex(this, filter); } @Nullable @Override public ProviderInternal getPending(String name) { return pendingMap.get(name); } @Override public void putPending(String name, ProviderInternal provider) { pendingMap.put(name, provider); } @Override public void removePending(String name) { pendingMap.remove(name); } @Override public void removePending(ProviderInternal provider) { pendingMap.values().remove(provider); } @Override public Map> getPendingAsMap() { return pendingMap; } } private static class FilteredIndex implements Index { private final Index delegate; private final CollectionFilter filter; FilteredIndex(Index delegate, CollectionFilter filter) { this.delegate = delegate; this.filter = filter; } @Override public void put(String name, T value) { throw new UnsupportedOperationException(); } @Override public T get(String name) { return filter.filter(delegate.get(name)); } @Override public void remove(String name) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public NavigableMap asMap() { NavigableMap delegateMap = delegate.asMap(); NavigableMap filtered = new TreeMap(); for (Map.Entry entry : delegateMap.entrySet()) { T obj = filter.filter(entry.getValue()); if (obj != null) { filtered.put(entry.getKey(), obj); } } return filtered; } @Override public Index filter(CollectionFilter filter) { return new FilteredIndex(delegate, this.filter.and(filter)); } @Nullable @Override public ProviderInternal getPending(String name) { ProviderInternal provider = delegate.getPending(name); if (provider != null && provider.getType() != null && filter.getType().isAssignableFrom(provider.getType())) { return (ProviderInternal) provider; } else { return null; } } @Override public void putPending(String name, ProviderInternal provider) { delegate.putPending(name, provider); } @Override public void removePending(String name) { delegate.removePending(name); } @Override public void removePending(ProviderInternal provider) { delegate.removePending(provider); } @Override public Map> getPendingAsMap() { // TODO not sure if we can clean up the generics here and do less unchecked casting Map> delegateMap = Cast.uncheckedCast(delegate.getPendingAsMap()); Map> filteredMap = Maps.newLinkedHashMap(); for (Map.Entry> entry : delegateMap.entrySet()) { if (entry.getValue().getType() != null && filter.getType().isAssignableFrom(entry.getValue().getType())) { ProviderInternal typedValue = Cast.uncheckedCast(entry.getValue()); filteredMap.put(entry.getKey(), typedValue); } } return filteredMap; } } public interface ElementInfo { String getName(); Class getType(); } private static class ObjectBackedElementInfo implements ElementInfo { private final String name; private final T o; ObjectBackedElementInfo(String name, T o) { this.name = name; this.o = o; } @Override public String getName() { return name; } @Override public Class getType() { return new DslObject(o).getDeclaredType(); } } private static class ProviderBackedElementInfo implements ElementInfo { private final String name; private final ProviderInternal provider; ProviderBackedElementInfo(String name, ProviderInternal provider) { this.name = name; this.provider = provider; } @Override public String getName() { return name; } @Override public Class getType() { return provider.getType(); } } @Nullable protected NamedDomainObjectProvider findDomainObject(String name) { NamedDomainObjectProvider provider = searchForDomainObject(name); // Run the rules and try to find something again. if (provider == null) { if (applyRules(name)) { return searchForDomainObject(name); } } return provider; } @Nullable private NamedDomainObjectProvider searchForDomainObject(String name) { // Look for a realized object T object = findByNameWithoutRules(name); if (object != null) { return createExistingProvider(name, object); } // Look for a provider with that name ProviderInternal provider = findByNameLaterWithoutRules(name); if (provider != null) { // TODO: Need to check for proper type/cast return Cast.uncheckedCast(provider); } return null; } protected NamedDomainObjectProvider createExistingProvider(String name, T object) { return Cast.uncheckedCast(getInstantiator().newInstance(ExistingNamedDomainObjectProvider.class, this, name, new DslObject(object).getDeclaredType())); } protected abstract class AbstractNamedDomainObjectProvider extends AbstractReadOnlyProvider implements Named, NamedDomainObjectProvider { private final String name; private final Class type; protected AbstractNamedDomainObjectProvider(String name, Class type) { this.name = name; this.type = type; } @Nullable @Override public Class getType() { return type; } @Override public String getName() { return name; } @Override public boolean isPresent() { return findDomainObject(getName()) != null; } @Override public String toString() { return String.format("provider(%s %s, %s)", getTypeDisplayName(), getName(), getType()); } } protected class ExistingNamedDomainObjectProvider extends AbstractNamedDomainObjectProvider { public ExistingNamedDomainObjectProvider(String name, Class type) { super(name, type); } @Override public void configure(Action action) { assertMutable("NamedDomainObjectProvider.configure(Action)"); withMutationDisabled(action).execute(get()); } @Override public boolean isPresent() { return getOrNull() != null; } @Override public I get() { if (!isPresent()) { throw domainObjectRemovedException(getName(), getType()); } return super.get(); } @Override public I getOrNull() { return Cast.uncheckedCast(findByNameWithoutRules(getName())); } } public abstract class AbstractDomainObjectCreatingProvider extends AbstractNamedDomainObjectProvider { private I object; private RuntimeException failure; protected ImmutableActionSet onCreate; private boolean removedBeforeRealized = false; public AbstractDomainObjectCreatingProvider(String name, Class type, @Nullable Action configureAction) { super(name, type); this.onCreate = ImmutableActionSet.empty().mergeFrom(getEventRegister().getAddActions()); if (configureAction != null) { configure(configureAction); } } @Override public boolean isPresent() { return findDomainObject(getName()) != null; } @Override public void configure(final Action action) { assertMutable("NamedDomainObjectProvider.configure(Action)"); Action wrappedAction = withMutationDisabled(action); Action decoratedAction = getEventRegister().getDecorator().decorate(wrappedAction); if (object != null) { // Already realized, just run the action now decoratedAction.execute(object); return; } // Collect any container level add actions then add the object specific action onCreate = onCreate.mergeFrom(getEventRegister().getAddActions()).add(decoratedAction); } @Override public I get() { if (wasElementRemoved()) { throw domainObjectRemovedException(getName(), getType()); } return super.get(); } @Override public I getOrNull() { if (wasElementRemoved()) { return null; } if (failure != null) { throw failure; } if (object == null) { object = getType().cast(findByNameWithoutRules(getName())); if (object == null) { tryCreate(); } } return object; } protected void tryCreate() { try { // Collect any container level add actions added since the last call to configure() onCreate = onCreate.mergeFrom(getEventRegister().getAddActions()); // Create the domain object object = createDomainObject(); // Register the domain object add(object, onCreate); realized(AbstractDomainObjectCreatingProvider.this); onLazyDomainObjectRealized(); } catch (Throwable ex) { failure = domainObjectCreationException(ex); throw failure; } finally { // Discard state that is no longer required onCreate = ImmutableActionSet.empty(); } } protected abstract I createDomainObject(); protected void onLazyDomainObjectRealized() { // Do nothing. } protected boolean wasElementRemoved() { // Check for presence as the domain object may have been replaced return (wasElementRemovedBeforeRealized() || wasElementRemovedAfterRealized()) && !isPresent(); } private boolean wasElementRemovedBeforeRealized() { return removedBeforeRealized; } private boolean wasElementRemovedAfterRealized() { return object != null && findByNameWithoutRules(getName()) == null; } protected RuntimeException domainObjectCreationException(Throwable cause) { return new IllegalStateException(String.format("Could not create domain object '%s' (%s)", getName(), getType().getSimpleName()), cause); } } private static RuntimeException domainObjectRemovedException(String name, Class type) { return new IllegalStateException(String.format("The domain object '%s' (%s) for this provider is no longer present in its container.", name, type.getSimpleName())); } }