/*
* Copyright 2013 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.artifacts.dsl;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.InvalidUserCodeException;
import org.gradle.api.artifacts.ComponentMetadataDetails;
import org.gradle.api.artifacts.ModuleIdentifier;
import org.gradle.api.artifacts.dsl.ComponentMetadataHandler;
import org.gradle.api.artifacts.ivy.IvyModuleDescriptor;
import org.gradle.api.internal.artifacts.ComponentMetadataProcessor;
import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory;
import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyModuleDescriptor;
import org.gradle.api.internal.artifacts.repositories.resolver.ComponentMetadataDetailsAdapter;
import org.gradle.api.internal.notations.ModuleIdentifierNotationConverter;
import org.gradle.api.specs.Spec;
import org.gradle.api.specs.Specs;
import org.gradle.internal.component.external.model.IvyModuleResolveMetadata;
import org.gradle.internal.component.external.model.ModuleComponentResolveMetadata;
import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetadata;
import org.gradle.internal.reflect.Instantiator;
import org.gradle.internal.resolve.ModuleVersionResolveException;
import org.gradle.internal.rules.DefaultRuleActionAdapter;
import org.gradle.internal.rules.DefaultRuleActionValidator;
import org.gradle.internal.rules.RuleAction;
import org.gradle.internal.rules.RuleActionAdapter;
import org.gradle.internal.rules.RuleActionValidator;
import org.gradle.internal.rules.SpecRuleAction;
import org.gradle.internal.typeconversion.NotationParser;
import org.gradle.internal.typeconversion.NotationParserBuilder;
import org.gradle.internal.typeconversion.UnsupportedNotationException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class DefaultComponentMetadataHandler implements ComponentMetadataHandler, ComponentMetadataProcessor {
private static final String ADAPTER_NAME = ComponentMetadataHandler.class.getSimpleName();
private static final List<Class<?>> VALIDATOR_PARAM_LIST = Collections.<Class<?>>singletonList(IvyModuleDescriptor.class);
private static final String INVALID_SPEC_ERROR = "Could not add a component metadata rule for module '%s'.";
private final Instantiator instantiator;
private final Set<SpecRuleAction<? super ComponentMetadataDetails>> rules = Sets.newLinkedHashSet();
private final RuleActionAdapter<ComponentMetadataDetails> ruleActionAdapter;
private final NotationParser<Object, ModuleIdentifier> moduleIdentifierNotationParser;
public DefaultComponentMetadataHandler(Instantiator instantiator, RuleActionAdapter<ComponentMetadataDetails> ruleActionAdapter, ImmutableModuleIdentifierFactory moduleIdentifierFactory) {
this.instantiator = instantiator;
this.ruleActionAdapter = ruleActionAdapter;
this.moduleIdentifierNotationParser = NotationParserBuilder
.toType(ModuleIdentifier.class)
.converter(new ModuleIdentifierNotationConverter(moduleIdentifierFactory))
.toComposite();
}
public DefaultComponentMetadataHandler(Instantiator instantiator, ImmutableModuleIdentifierFactory moduleIdentifierFactory) {
this(instantiator, createAdapter(), moduleIdentifierFactory);
}
private static RuleActionAdapter<ComponentMetadataDetails> createAdapter() {
RuleActionValidator<ComponentMetadataDetails> ruleActionValidator = new DefaultRuleActionValidator<ComponentMetadataDetails>(VALIDATOR_PARAM_LIST);
return new DefaultRuleActionAdapter<ComponentMetadataDetails>(ruleActionValidator, ADAPTER_NAME);
}
private ComponentMetadataHandler addRule(SpecRuleAction<? super ComponentMetadataDetails> ruleAction) {
rules.add(ruleAction);
return this;
}
private SpecRuleAction<? super ComponentMetadataDetails> createAllSpecRuleAction(RuleAction<? super ComponentMetadataDetails> ruleAction) {
return new SpecRuleAction<ComponentMetadataDetails>(ruleAction, Specs.<ComponentMetadataDetails>satisfyAll());
}
private SpecRuleAction<? super ComponentMetadataDetails> createSpecRuleActionForModule(Object id, RuleAction<? super ComponentMetadataDetails> ruleAction) {
ModuleIdentifier moduleIdentifier;
try {
moduleIdentifier = moduleIdentifierNotationParser.parseNotation(id);
} catch (UnsupportedNotationException e) {
throw new InvalidUserCodeException(String.format(INVALID_SPEC_ERROR, id == null ? "null" : id.toString()), e);
}
Spec<ComponentMetadataDetails> spec = new ComponentMetadataDetailsMatchingSpec(moduleIdentifier);
return new SpecRuleAction<ComponentMetadataDetails>(ruleAction, spec);
}
public ComponentMetadataHandler all(Action<? super ComponentMetadataDetails> rule) {
return addRule(createAllSpecRuleAction(ruleActionAdapter.createFromAction(rule)));
}
public ComponentMetadataHandler all(Closure<?> rule) {
return addRule(createAllSpecRuleAction(ruleActionAdapter.createFromClosure(ComponentMetadataDetails.class, rule)));
}
public ComponentMetadataHandler all(Object ruleSource) {
return addRule(createAllSpecRuleAction(ruleActionAdapter.createFromRuleSource(ComponentMetadataDetails.class, ruleSource)));
}
public ComponentMetadataHandler withModule(Object id, Action<? super ComponentMetadataDetails> rule) {
return addRule(createSpecRuleActionForModule(id, ruleActionAdapter.createFromAction(rule)));
}
public ComponentMetadataHandler withModule(Object id, Closure<?> rule) {
return addRule(createSpecRuleActionForModule(id, ruleActionAdapter.createFromClosure(ComponentMetadataDetails.class, rule)));
}
public ComponentMetadataHandler withModule(Object id, Object ruleSource) {
return addRule(createSpecRuleActionForModule(id, ruleActionAdapter.createFromRuleSource(ComponentMetadataDetails.class, ruleSource)));
}
public ModuleComponentResolveMetadata processMetadata(ModuleComponentResolveMetadata metadata) {
ModuleComponentResolveMetadata updatedMetadata;
if (rules.isEmpty()) {
updatedMetadata = metadata;
} else {
MutableModuleComponentResolveMetadata mutableMetadata = metadata.asMutable();
ComponentMetadataDetails details = instantiator.newInstance(ComponentMetadataDetailsAdapter.class, mutableMetadata);
processAllRules(metadata, details);
updatedMetadata = mutableMetadata.asImmutable();
}
if (!updatedMetadata.getStatusScheme().contains(updatedMetadata.getStatus())) {
throw new ModuleVersionResolveException(updatedMetadata.getId(), String.format("Unexpected status '%s' specified for %s. Expected one of: %s", updatedMetadata.getStatus(), updatedMetadata.getComponentId().getDisplayName(), updatedMetadata.getStatusScheme()));
}
return updatedMetadata;
}
private void processAllRules(ModuleComponentResolveMetadata metadata, ComponentMetadataDetails details) {
for (SpecRuleAction<? super ComponentMetadataDetails> rule : rules) {
processRule(rule, metadata, details);
}
}
private void processRule(SpecRuleAction<? super ComponentMetadataDetails> specRuleAction, ModuleComponentResolveMetadata metadata, final ComponentMetadataDetails details) {
if (!specRuleAction.getSpec().isSatisfiedBy(details)) {
return;
}
final List<Object> inputs = Lists.newArrayList();
final RuleAction<? super ComponentMetadataDetails> action = specRuleAction.getAction();
for (Class<?> inputType : action.getInputTypes()) {
if (inputType == IvyModuleDescriptor.class) {
// Ignore the rule if it expects Ivy metadata and this isn't an Ivy module
if (!(metadata instanceof IvyModuleResolveMetadata)) {
return;
}
IvyModuleResolveMetadata ivyMetadata = (IvyModuleResolveMetadata) metadata;
inputs.add(new DefaultIvyModuleDescriptor(ivyMetadata.getExtraInfo(), ivyMetadata.getBranch(), ivyMetadata.getStatus()));
continue;
}
// We've already validated the inputs: should never get here.
throw new IllegalStateException();
}
try {
synchronized (this) {
action.execute(details, inputs);
}
} catch (Exception e) {
throw new InvalidUserCodeException(String.format("There was an error while evaluating a component metadata rule for %s.", details.getId()), e);
}
}
static class ComponentMetadataDetailsMatchingSpec implements Spec<ComponentMetadataDetails> {
private ModuleIdentifier target;
ComponentMetadataDetailsMatchingSpec(ModuleIdentifier target) {
this.target = target;
}
public boolean isSatisfiedBy(ComponentMetadataDetails componentMetadataDetails) {
return componentMetadataDetails.getId().getGroup().equals(target.getGroup()) && componentMetadataDetails.getId().getName().equals(target.getName());
}
}
}