/* * 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.registry; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.gradle.model.internal.core.ModelNode; import org.gradle.model.internal.core.ModelPath; import org.gradle.model.internal.type.ModelType; import java.util.*; class RuleBindings { private final NodeAtStateIndex rulesBySubject; private final NodeAtStateIndex rulesByInput; private final PathPredicateIndex untypedPathReferences = new PathPredicateIndex(); private final PathPredicateIndex typedPathReferences = new PathPredicateIndex(); private final TypePredicateIndex scopeReferences = new TypePredicateIndex(); public RuleBindings() { rulesBySubject = new NodeAtStateIndex("rulesBySubject"); rulesByInput = new NodeAtStateIndex("rulesByInput"); } public void nodeCreated(ModelNodeInternal node) { untypedPathReferences.addNode(node); } public void nodeDiscovered(ModelNodeInternal node) { typedPathReferences.addNode(node); scopeReferences.addNodeToScope(node.getPath(), node); scopeReferences.addNodeToScope(node.getPath().getParent(), node); } private void bound(Reference reference, ModelNodeInternal node) { ModelBinding binding = reference.binding; binding.onBind(node); reference.index.put(new NodeAtState(node.getPath(), binding.predicate.getState()), reference.owner); } public void remove(ModelNodeInternal node) { untypedPathReferences.removeNode(node); typedPathReferences.removeNode(node); scopeReferences.removeNodeFromScope(node.getPath(), node); scopeReferences.removeNodeFromScope(node.getPath().getParent(), node); rulesBySubject.nodeRemoved(node); rulesByInput.nodeRemoved(node); } public void add(RuleBinder ruleBinder) { addRule(ruleBinder, rulesBySubject, ruleBinder.getSubjectBinding()); for (ModelBinding binding : ruleBinder.getInputBindings()) { addRule(ruleBinder, rulesByInput, binding); } } private void addRule(RuleBinder rule, NodeAtStateIndex index, ModelBinding binding) { Reference reference = new Reference(rule, index, binding); BindingPredicate predicate = binding.getPredicate(); if (predicate.getPath() != null) { if (predicate.getScope() != null) { throw new UnsupportedOperationException("Currently not implemented"); } if (reference.binding.canBindInState(ModelNode.State.Registered)) { untypedPathReferences.addReference(reference); } else { typedPathReferences.addReference(reference); } } else if (predicate.getScope() != null) { scopeReferences.addReference(reference); } else { throw new UnsupportedOperationException("Currently not implemented"); } } private static void unbind(RuleBinder rule, ModelNodeInternal node) { rule.getSubjectBinding().onUnbind(node); for (ModelBinding binding : rule.getInputBindings()) { binding.onUnbind(node); } } /** * Returns the set of rules with the given target as their subject. */ public Collection<RuleBinder> getRulesWithSubject(NodeAtState target) { return rulesBySubject.get(target); } /** * Returns the set of rules with the given input. */ public Collection<RuleBinder> getRulesWithInput(NodeAtState input) { return rulesByInput.get(input); } private static class Reference { final ModelBinding binding; final NodeAtStateIndex index; final RuleBinder owner; public Reference(RuleBinder owner, NodeAtStateIndex index, ModelBinding binding) { this.owner = owner; this.index = index; this.binding = binding; } @Override public String toString() { return binding + " in " + index.name; } } private class PredicateMatches { final List<Reference> references = new ArrayList<Reference>(); ModelNodeInternal match; void match(ModelNodeInternal node) { for (Reference reference : references) { bound(reference, node); } match = node; } void add(Reference reference) { references.add(reference); if (match != null) { bound(reference, match); } } public void remove(ModelNodeInternal node) { match = null; } } private class PathPredicateIndex { final Map<ModelPath, PredicateMatches> predicates = Maps.newLinkedHashMap(); public void addNode(ModelNodeInternal node) { predicatesForPath(node.getPath()).match(node); } public void addReference(Reference reference) { ModelPath path = reference.binding.getPredicate().getPath(); predicatesForPath(path).add(reference); } private PredicateMatches predicatesForPath(ModelPath path) { PredicateMatches predicatesForReference = predicates.get(path); if (predicatesForReference == null) { predicatesForReference = new PredicateMatches(); predicates.put(path, predicatesForReference); } return predicatesForReference; } public void removeNode(ModelNodeInternal node) { predicatesForPath(node.getPath()).remove(node); } } private class ScopeIndex { final Map<ModelType<?>, PredicateMatches> types = Maps.newLinkedHashMap(); final List<ModelNodeInternal> nodes = Lists.newArrayList(); public void addNode(ModelNodeInternal node) { nodes.add(node); for (Map.Entry<ModelType<?>, PredicateMatches> entry : types.entrySet()) { if (node.canBeViewedAs(entry.getKey())) { entry.getValue().match(node); } } } public void removeNode(ModelNodeInternal node) { nodes.remove(node); for (PredicateMatches matches : types.values()) { if (matches.match == node) { matches.remove(node); } } } public void addReference(Reference reference) { ModelType<?> type = reference.binding.getPredicate().getType(); PredicateMatches predicateMatches = types.get(type); boolean newType = predicateMatches == null; if (predicateMatches == null) { predicateMatches = new PredicateMatches(); types.put(type, predicateMatches); } predicateMatches.add(reference); if (newType) { for (ModelNodeInternal node : nodes) { if (node.canBeViewedAs(type)) { predicateMatches.match(node); } } } } } private class TypePredicateIndex { final Map<ModelPath, ScopeIndex> scopes = Maps.newLinkedHashMap(); public void addNodeToScope(ModelPath path, ModelNodeInternal node) { scopeForPath(path).addNode(node); } public void removeNodeFromScope(ModelPath path, ModelNodeInternal node) { scopeForPath(path).removeNode(node); } public void addReference(Reference reference) { ModelPath path = reference.binding.getPredicate().getScope(); scopeForPath(path).addReference(reference); } private ScopeIndex scopeForPath(ModelPath path) { ScopeIndex scope = scopes.get(path); if (scope == null) { scope = new ScopeIndex(); scopes.put(path, scope); } return scope; } } private static class NodeAtStateIndex { private final EnumMap<ModelNode.State, Map<String, List<RuleBinder>>> boundAtState = Maps.newEnumMap(ModelNode.State.class); private final String name; private NodeAtStateIndex(String name) { this.name = name; } private Map<String, List<RuleBinder>> getByState(ModelNode.State state) { Map<String, List<RuleBinder>> map = boundAtState.get(state); if (map == null) { map = new HashMap<String, List<RuleBinder>>(64); boundAtState.put(state, map); } return map; } public void nodeRemoved(ModelNodeInternal node) { // This could be more efficient; assume that removal happens much less often than addition for (ModelNode.State state : ModelNode.State.values()) { Map<String, List<RuleBinder>> byState = getByState(state); List<RuleBinder> remove = byState.remove(node.getPath().toString()); if (remove != null) { for (RuleBinder rule : remove) { unbind(rule, node); } } } } public void put(NodeAtState nodeAtState, RuleBinder binder) { Map<String, List<RuleBinder>> byState = getByState(nodeAtState.state); String path = nodeAtState.path.toString(); List<RuleBinder> byPath = getByPath(byState, path); if (!byPath.contains(binder)) { byPath.add(binder); } } private List<RuleBinder> getByPath(Map<String, List<RuleBinder>> byState, String path) { List<RuleBinder> ruleBinders = byState.get(path); if (ruleBinders == null) { ruleBinders = new LinkedList<RuleBinder>(); byState.put(path, ruleBinders); } return ruleBinders; } /** * Returns rules for given target at state. */ public Collection<RuleBinder> get(NodeAtState nodeAtState) { return getByPath(getByState(nodeAtState.state), nodeAtState.path.toString()); } public void remove(ModelNodeInternal node, RuleBinder ruleBinder) { unbind(ruleBinder, node); for (ModelNode.State state : ModelNode.State.values()) { Map<String, List<RuleBinder>> byState = getByState(state); getByPath(byState, node.getPath().toString()).clear(); } } @Override public String toString() { return name; } } }