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.11.1
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 groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.NamedDomainObjectCollection;
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.specs.Spec;
import org.gradle.api.specs.Specs;
import org.gradle.internal.metaobject.AbstractDynamicObject;
import org.gradle.internal.metaobject.DynamicObject;
import org.gradle.internal.metaobject.GetPropertyResult;
import org.gradle.internal.metaobject.InvokeMethodResult;
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 java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;

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

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

    private final ContainerElementsDynamicObject elementsDynamicObject = new ContainerElementsDynamicObject();

    private final List rules = new ArrayList();
    private final Set applyingRulesFor = new HashSet();

    public DefaultNamedDomainObjectCollection(Class type, Collection store, Instantiator instantiator, Namer namer) {
        super(type, store);
        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, Collection 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);
    }

    /**
     * Subclasses that can guarantee that the backing store enforces name uniqueness should override this to simply call super.add(T) (avoiding an unnecessary lookup)
     */
    public boolean add(T o) {
        if (!hasWithName(namer.determineName(o))) {
            return super.add(o);
        } else {
            handleAttemptToAddItemWithNonUniqueName(o);
            return false;
        }
    }

    @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));
    }


    /**
     * 

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)); } 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. */ 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(); } public SortedMap getAsMap() { return index.asMap(); } public SortedSet getNames() { return index.asMap().navigableKeySet(); } public NamedDomainObjectCollection withType(Class type) { return filtered(createFilter(type)); } public NamedDomainObjectCollection matching(Spec spec) { return filtered(createFilter(spec)); } public NamedDomainObjectCollection matching(Closure spec) { return matching(Specs.convertClosureToSpec(spec)); } public T findByName(String name) { T value = findByNameWithoutRules(name); if (value != null) { return value; } if (!applyRules(name)) { return null; } return findByNameWithoutRules(name); } protected boolean hasWithName(String name) { return findByNameWithoutRules(name) != null; } protected T findByNameWithoutRules(String name) { return index.get(name); } protected T removeByName(String name) { T it = getByName(name); if (it != null) { 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)); } } else { return null; } } public T getByName(String name) throws UnknownDomainObjectException { T t = findByName(name); if (t == null) { throw createNotFoundException(name); } return t; } public T getByName(String name, Closure configureClosure) throws UnknownDomainObjectException { T t = getByName(name); ConfigureUtil.configure(configureClosure, t); return t; } @Override public T getByName(String name, Action configureAction) throws UnknownDomainObjectException { T t = getByName(name); configureAction.execute(t); return t; } public T getAt(String name) throws UnknownDomainObjectException { return getByName(name); } @Override public MethodAccess getAdditionalMethods() { return getElementsAsDynamicObject(); } @Override public PropertyAccess getAdditionalProperties() { return getElementsAsDynamicObject(); } protected DynamicObject getElementsAsDynamicObject() { return elementsDynamicObject; } /** * @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; } public Rule addRule(Rule rule) { rules.add(rule); return rule; } 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; } } 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 void getProperty(String name, GetPropertyResult result) { T t = findByName(name); if (t != null) { result.result(t); } } @Override public Map getProperties() { return getAsMap(); } @Override public boolean hasMethod(String name, Object... arguments) { return isConfigureMethod(name, arguments); } @Override public void invokeMethod(String name, InvokeMethodResult result, Object... arguments) { if (isConfigureMethod(name, arguments)) { result.result(ConfigureUtil.configure((Closure) arguments[0], getByName(name))); } } 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); T get(String name); void remove(String name); void clear(); NavigableMap asMap(); Index filter(CollectionFilter filter); } protected static class UnfilteredIndex implements Index { 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(); } @Override public Index filter(CollectionFilter filter) { return new FilteredIndex(this, filter); } } private static class FilteredIndex implements Index { private final Index delegate; private final CollectionFilter filter; public 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)); } } }