/* * Copyright 2015 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.model.internal.core; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import org.gradle.api.Action; import org.gradle.api.Nullable; import org.gradle.api.Transformer; import org.gradle.api.specs.Spec; import org.gradle.internal.Cast; import org.gradle.internal.Factories; import org.gradle.model.InvalidModelRuleException; import org.gradle.model.ModelMap; import org.gradle.model.ModelRuleBindingException; import org.gradle.model.RuleSource; import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor; import org.gradle.model.internal.inspect.ModelElementProjection; import org.gradle.model.internal.manage.instance.ManagedInstance; import org.gradle.model.internal.report.IncompatibleTypeReferenceReporter; import org.gradle.model.internal.type.ModelType; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import static org.gradle.internal.Cast.uncheckedCast; import static org.gradle.model.internal.core.NodeInitializerContext.forExtensibleType; import static org.gradle.model.internal.core.NodePredicate.allLinks; // TODO - mix Groovy DSL support in public class NodeBackedModelMap<T> extends ModelMapGroovyView<T> implements ManagedInstance { private static final ElementFilter NO_PARENT = new ElementFilter(ModelType.UNTYPED) { @Override public boolean apply(MutableModelNode node) { return true; } @Override public boolean isSatisfiedBy(ModelType<?> element) { return true; } @Override public void validateCanBindAction(MutableModelNode node, ModelAction action) {} @Override public void validateCanCreateElement(ModelPath path, ModelType<?> type) {} }; private final ModelType<T> elementType; private final ModelRuleDescriptor sourceDescriptor; private final MutableModelNode modelNode; private final ModelViewState viewState; private final ChildNodeInitializerStrategy<? super T> creatorStrategy; private final ElementFilter elementFilter; private final ModelType<?> publicType; // Note: used by generated subtypes public NodeBackedModelMap(ModelType<?> publicType, ModelType<T> elementType, ModelRuleDescriptor sourceDescriptor, MutableModelNode modelNode, ModelViewState viewState, ChildNodeInitializerStrategy<? super T> creatorStrategy) { this(publicType, elementType, sourceDescriptor, modelNode, viewState, NO_PARENT, creatorStrategy); } private NodeBackedModelMap(ModelType<?> publicType, ModelType<T> elementType, ModelRuleDescriptor sourceDescriptor, MutableModelNode modelNode, ModelViewState viewState, ElementFilter parentFilter, ChildNodeInitializerStrategy<? super T> creatorStrategy) { this.publicType = publicType; this.viewState = viewState; this.creatorStrategy = creatorStrategy; this.elementType = elementType; this.modelNode = modelNode; this.sourceDescriptor = sourceDescriptor; this.elementFilter = parentFilter.withType(elementType); } public static <T> ChildNodeInitializerStrategy<T> createUsingRegistry(final NodeInitializerRegistry nodeInitializerRegistry) { return new ChildNodeInitializerStrategy<T>() { @Override public <S extends T> NodeInitializer initializer(ModelType<S> type, Spec<ModelType<?>> constraints) { return nodeInitializerRegistry.getNodeInitializer(forExtensibleType(type, constraints)); } }; } public static <T> ChildNodeInitializerStrategy<T> createUsingParentNode(final ModelType<T> baseItemModelType) { return createUsingParentNode(new Transformer<NamedEntityInstantiator<T>, MutableModelNode>() { @Override public NamedEntityInstantiator<T> transform(MutableModelNode modelNode) { return modelNode.getPrivateData(instantiatorTypeOf(baseItemModelType)); } }); } public static <T> ChildNodeInitializerStrategy<T> createUsingParentNode(final Transformer<? extends NamedEntityInstantiator<T>, ? super MutableModelNode> instantiatorTransform) { return new ChildNodeInitializerStrategy<T>() { @Override public <S extends T> NodeInitializer initializer(final ModelType<S> type, Spec<ModelType<?>> constraints) { return new NodeInitializer() { @Override public Multimap<ModelActionRole, ModelAction> getActions(ModelReference<?> subject, ModelRuleDescriptor descriptor) { return ImmutableSetMultimap.<ModelActionRole, ModelAction>builder() .put(ModelActionRole.Discover, AddProjectionsAction.of(subject, descriptor, UnmanagedModelProjection.of(type), new ModelElementProjection(type) )) .put(ModelActionRole.Create, DirectNodeNoInputsModelAction.of(subject, descriptor, new Action<MutableModelNode>() { @Override public void execute(MutableModelNode modelNode) { NamedEntityInstantiator<T> instantiator = instantiatorTransform.transform(modelNode.getParent()); S item = instantiator.create(modelNode.getPath().getName(), type.getConcreteClass()); modelNode.setPrivateData(type, item); } })) .build(); } }; } }; } private static <I> ModelType<NamedEntityInstantiator<I>> instantiatorTypeOf(ModelType<I> type) { return new ModelType.Builder<NamedEntityInstantiator<I>>() { }.where( new ModelType.Parameter<I>() { }, type ).build(); } @Override public String getName() { return modelNode.getPath().getName(); } @Override public MutableModelNode getBackingNode() { return modelNode; } @Override public ModelType<?> getManagedType() { return ModelType.of(this.getClass()); } private <E> void mutateChildren(ModelActionRole role, ModelType<E> filterType, String operation, Action<? super E> configAction) { viewState.assertCanMutate(); ModelRuleDescriptor descriptor = sourceDescriptor.append(operation); ModelReference<E> subject = ModelReference.of(filterType); modelNode.applyTo(allLinks(elementFilter.withType(filterType)), role, NoInputsModelAction.of(subject, descriptor, configAction)); } private <E> void mutateChildren(ModelActionRole role, ModelType<E> filterType, DeferredModelAction configAction) { viewState.assertCanMutate(); ModelReference<E> subject = ModelReference.of(filterType); modelNode.defineRulesFor(allLinks(elementFilter.withType(filterType)), role, new DeferredActionWrapper<E>(subject, role, configAction)); } @Override public <S> void afterEach(Class<S> type, Action<? super S> configAction) { mutateChildren(ModelActionRole.Finalize, ModelType.of(type), "afterEach()", configAction); } // Called from transformed DSL rules public <S> void afterEach(Class<S> type, DeferredModelAction configAction) { mutateChildren(ModelActionRole.Finalize, ModelType.of(type), configAction); } @Override public void afterEach(Action<? super T> configAction) { mutateChildren(ModelActionRole.Finalize, elementType, "afterEach()", configAction); } // Called from transformed DSL rules public void afterEach(DeferredModelAction configAction) { mutateChildren(ModelActionRole.Finalize, elementType, configAction); } @Override public void all(Action<? super T> configAction) { mutateChildren(ModelActionRole.Mutate, elementType, "all()", configAction); } // Called from transformed DSL rules public void all(DeferredModelAction configAction) { mutateChildren(ModelActionRole.Initialize, elementType, configAction); } @Override public void beforeEach(Action<? super T> configAction) { mutateChildren(ModelActionRole.Defaults, elementType, "beforeEach()", configAction); } // Called from transformed DSL rules public void beforeEach(DeferredModelAction configAction) { mutateChildren(ModelActionRole.Defaults, elementType, configAction); } @Override public <S> void beforeEach(Class<S> type, Action<? super S> configAction) { mutateChildren(ModelActionRole.Defaults, ModelType.of(type), "beforeEach()", configAction); } // Called from transformed DSL rules public <S> void beforeEach(Class<S> type, DeferredModelAction configAction) { mutateChildren(ModelActionRole.Defaults, ModelType.of(type), configAction); } @Override public boolean containsKey(Object name) { if (!(name instanceof String)) { viewState.assertCanReadChildren(); return false; } viewState.assertCanReadChild((String) name); return modelNode.hasLink((String) name, elementFilter); } @Override public boolean containsValue(Object item) { throw new UnsupportedOperationException("Not implemented yet."); } @Override public void create(final String name) { doCreate(name, elementType, (Action<? super T>) null); } @Override public void create(String name, Action<? super T> configAction) { doCreate(name, elementType, configAction); } // Called from transformed DSL rules public void create(String name, DeferredModelAction configAction) { doCreate(name, elementType, configAction); } @Override public <S extends T> void create(String name, Class<S> type) { doCreate(name, ModelType.of(type), (Action<? super T>) null); } @Override public <S extends T> void create(String name, Class<S> type, Action<? super S> configAction) { doCreate(name, ModelType.of(type), configAction); } // Called from transformed DSL rules public <S extends T> void create(String name, Class<S> type, DeferredModelAction configAction) { doCreate(name, ModelType.of(type), configAction); } @Override public void put(String name, T instance) { Class<T> type = Cast.uncheckedCast(instance.getClass()); ModelRuleDescriptor descriptor = sourceDescriptor.append("put()"); if (instance instanceof ManagedInstance) { ManagedInstance target = (ManagedInstance) instance; modelNode.addReference(name, target.getManagedType(), target.getBackingNode(), descriptor); } else { modelNode.addLink( ModelRegistrations.unmanagedInstance( ModelReference.of(modelNode.getPath().child(name), type), Factories.constant(instance) ) .descriptor(descriptor) .build() ); } } private <S extends T> void doCreate(String name, ModelType<S> type, final DeferredModelAction action) { ModelPath childPath = modelNode.getPath().child(name); doCreate(childPath, type, action.getDescriptor(), DirectNodeNoInputsModelAction.of(ModelReference.of(childPath, type), action.getDescriptor(), new Action<MutableModelNode>() { @Override public void execute(MutableModelNode node) { action.execute(node, ModelActionRole.Initialize); } })); } private <S extends T> void doCreate(String name, ModelType<S> type, @Nullable Action<? super S> initAction) { ModelPath childPath = modelNode.getPath().child(name); ModelRuleDescriptor descriptor = sourceDescriptor.append("create(%s)", name); if (initAction != null) { doCreate(childPath, type, descriptor, NoInputsModelAction.of(ModelReference.of(childPath, type), descriptor, initAction)); } else { doCreate(childPath, type, descriptor, null); } } private <S extends T> void doCreate(ModelPath childPath, ModelType<S> type, ModelRuleDescriptor descriptor, @Nullable ModelAction initAction) { viewState.assertCanMutate(); elementFilter.validateCanCreateElement(childPath, type); NodeInitializer nodeInitializer = creatorStrategy.initializer(type, elementFilter); ModelRegistrations.Builder builder = ModelRegistrations.of(childPath, nodeInitializer).descriptor(descriptor); if (initAction != null) { builder.action(ModelActionRole.Initialize, initAction); } ModelRegistration registration = builder.build(); modelNode.addLink(registration); } @Nullable @Override public T get(Object name) { return get((String) name); } @Nullable @Override public T get(String name) { // TODO - lock this down MutableModelNode link = modelNode.getLink(name); if (link == null) { return null; } viewState.assertCanReadChild(name); link.ensureUsable(); if (!elementFilter.apply(link)) { return null; } if (viewState.isCanMutate()) { return link.asMutable(elementType, sourceDescriptor).getInstance(); } else { return link.asImmutable(elementType, sourceDescriptor).getInstance(); } } @Override public boolean isEmpty() { return size() == 0; } @Override public Set<String> keySet() { viewState.assertCanReadChildren(); return ImmutableSet.copyOf(modelNode.getLinkNames(elementFilter)); } @Override public int size() { viewState.assertCanReadChildren(); return modelNode.getLinkCount(elementFilter); } @Override public void named(String name, Action<? super T> configAction) { viewState.assertCanMutate(); ModelRuleDescriptor descriptor = sourceDescriptor.append("named(%s)", name); ModelReference<T> subject = ModelReference.of(modelNode.getPath().child(name), elementType); modelNode.applyToLink(ModelActionRole.Mutate, new FilteringActionWrapper<T>(elementFilter, subject, NoInputsModelAction.of(subject, descriptor, configAction))); } @Override public void named(String name, Class<? extends RuleSource> ruleSource) { viewState.assertCanMutate(); ModelRuleDescriptor descriptor = sourceDescriptor.append("named(%s, %s)", name, ruleSource.getName()); ModelReference<T> subject = ModelReference.of(modelNode.getPath().child(name), elementType); modelNode.defineRulesForLink(ModelActionRole.Defaults, new FilteringActionWrapper<T>(elementFilter, subject, DirectNodeNoInputsModelAction.of(subject, descriptor, new ApplyRuleSource(ruleSource)))); } // Called from transformed DSL rules public void named(String name, final DeferredModelAction action) { viewState.assertCanMutate(); ModelReference<T> subject = ModelReference.of(modelNode.getPath().child(name), elementType); modelNode.applyToLink(ModelActionRole.Initialize, new FilteringActionWrapper<T>(elementFilter, subject, new DeferredActionWrapper<T>(subject, ModelActionRole.Mutate, action))); } @Override public String getDisplayName() { return publicType.getDisplayName() + " '" + modelNode.getPath() + "'"; } @Override public Collection<T> values() { Iterable<T> values = Iterables.transform(keySet(), new Function<String, T>() { public T apply(@Nullable String name) { return get(name); } }); return Lists.newArrayList(values); } @Override public Iterator<T> iterator() { viewState.assertCanReadChildren(); return Iterators.transform(keySet().iterator(), new Function<String, T>() { @Override public T apply(@Nullable String name) { return get(name); } }); } @Override public <S> void withType(Class<S> type, Action<? super S> configAction) { mutateChildren(ModelActionRole.Mutate, ModelType.of(type), "withType()", configAction); } // Called from transformed DSL rules public <S> void withType(Class<S> type, DeferredModelAction configAction) { mutateChildren(ModelActionRole.Mutate, ModelType.of(type), configAction); } @Override public <S> void withType(Class<S> type, Class<? extends RuleSource> rules) { viewState.assertCanMutate(); modelNode.applyTo(allLinks(elementFilter.withType(type)), rules); } @Override public <S> ModelMap<S> withType(Class<S> typeClass) { ModelType<S> type = ModelType.of(typeClass); return withType(type); } public <S> ModelMap<S> withType(ModelType<S> type) { if (type.equals(elementType)) { return uncheckedCast(this); } ChildNodeInitializerStrategy<S> creatorStrategy1 = uncheckedCast(this.creatorStrategy); return new NodeBackedModelMap<S>(publicType, type, sourceDescriptor, modelNode, viewState, elementFilter, creatorStrategy1); } @Override public Void methodMissing(String name, Object argsObj) { Object[] args = (Object[]) argsObj; if (args.length == 2 && args[0] instanceof Class<?> && args[1] instanceof DeferredModelAction) { // Called from transformed DSL rules Class<? extends T> itemType = uncheckedCast(args[0]); DeferredModelAction action = uncheckedCast(args[1]); doCreate(name, ModelType.of(itemType), action); return null; } if (args.length == 1 && args[0] instanceof DeferredModelAction) { // Called from transformed DSL rules DeferredModelAction action = uncheckedCast(args[0]); named(name, action); return null; } return super.methodMissing(name, argsObj); } private static abstract class ElementFilter implements Predicate<MutableModelNode>, Spec<ModelType<?>> { protected final ModelType<?> elementType; public ElementFilter(ModelType<?> elementType) { this.elementType = elementType; } public ElementFilter withType(Class<?> elementType) { return withType(ModelType.of(elementType)); } public ElementFilter withType(ModelType<?> elementType) { if (this.elementType.equals(elementType)) { return this; } else { return new ChainedElementFilter(this, elementType); } } public abstract void validateCanBindAction(MutableModelNode node, ModelAction action); public abstract void validateCanCreateElement(ModelPath path, ModelType<?> type); } private static class ChainedElementFilter extends ElementFilter { private final ElementFilter parent; public ChainedElementFilter(ElementFilter parent, ModelType<?> elementType) { super(elementType); this.parent = parent; } @Override public boolean isSatisfiedBy(ModelType<?> element) { return elementType.isAssignableFrom(element) && parent.isSatisfiedBy(element); } @Override public boolean apply(MutableModelNode node) { node.ensureAtLeast(ModelNode.State.Discovered); return node.canBeViewedAs(elementType) && parent.apply(node); } @Override public void validateCanBindAction(MutableModelNode node, ModelAction action) { node.ensureAtLeast(ModelNode.State.Discovered); if (!node.canBeViewedAs(elementType)) { throw new InvalidModelRuleException(action.getDescriptor(), new ModelRuleBindingException( IncompatibleTypeReferenceReporter.of(node, elementType, action.getSubject().getDescription(), true).asString() )); } parent.validateCanBindAction(node, action); } @Override public void validateCanCreateElement(ModelPath path, ModelType<?> type) { if (!elementType.isAssignableFrom(type)) { throw new IllegalArgumentException(String.format("Cannot create '%s' with type '%s' as this is not a subtype of '%s'.", path, type, elementType)); } parent.validateCanCreateElement(path, type); } } private static class DeferredActionWrapper<T> extends AbstractModelAction<T> { private final ModelActionRole role; private final DeferredModelAction action; public DeferredActionWrapper(ModelReference<T> subject, ModelActionRole role, DeferredModelAction action) { super(subject, action.getDescriptor(), Collections.<ModelReference<?>>emptyList()); this.role = role; this.action = action; } @Override public void execute(MutableModelNode node, List<ModelView<?>> inputs) { action.execute(node, role); } } private static class FilteringActionWrapper<T> extends AbstractModelAction<T> { private final ElementFilter elementFilter; private final ModelAction delegate; public FilteringActionWrapper(ElementFilter elementFilter, ModelReference<T> subject, ModelAction delegate) { super(subject, delegate.getDescriptor(), delegate.getInputs()); this.elementFilter = elementFilter; this.delegate = delegate; } @Override public void execute(MutableModelNode modelNode, List<ModelView<?>> inputs) { elementFilter.validateCanBindAction(modelNode, delegate); delegate.execute(modelNode, inputs); } } private static class ApplyRuleSource implements Action<MutableModelNode> { private final ModelType<? extends RuleSource> rules; public ApplyRuleSource(Class<? extends RuleSource> rules) { this.rules = ModelType.of(rules); } @Override public void execute(MutableModelNode node) { node.applyToSelf(rules.getConcreteClass()); } } }