/*
* 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.dsl.internal.transform;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import groovy.lang.Closure;
import org.gradle.api.Nullable;
import org.gradle.api.Transformer;
import org.gradle.api.internal.ClosureBackedAction;
import org.gradle.internal.BiAction;
import org.gradle.internal.file.RelativeFilePathResolver;
import org.gradle.model.dsl.internal.inputs.PotentialInput;
import org.gradle.model.dsl.internal.inputs.PotentialInputs;
import org.gradle.model.internal.core.*;
import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor;
import org.gradle.model.internal.manage.instance.ManagedInstance;
import org.gradle.model.internal.type.ModelType;
import java.net.URI;
import java.util.List;
import java.util.Map;
public class ClosureBackedRuleFactory {
private static final ModelType<ManagedInstance> MANAGED_INSTANCE_TYPE = ModelType.of(ManagedInstance.class);
private final Transformer<SourceLocation, TransformedClosure> ruleLocationExtractor;
public ClosureBackedRuleFactory(RelativeFilePathResolver relativeFilePathResolver) {
this.ruleLocationExtractor = new RelativePathSourceLocationTransformer(relativeFilePathResolver);
}
/**
* Used by generated code. See {@link RuleVisitor}.
*/
@SuppressWarnings("unused")
public static Object decorate(@Nullable ClosureBackedRuleFactory factory, Closure<?> closure) {
return factory == null ? closure : factory.toAction(Object.class, closure);
}
public <T> DeferredModelAction toAction(final Class<T> subjectType, final Closure<?> closure) {
final TransformedClosure transformedClosure = (TransformedClosure) closure;
SourceLocation sourceLocation = ruleLocationExtractor.transform(transformedClosure);
final ModelRuleDescriptor descriptor = sourceLocation.asDescriptor();
return new DeferredModelAction() {
@Override
public ModelRuleDescriptor getDescriptor() {
return descriptor;
}
@Override
public void execute(MutableModelNode node, ModelActionRole role) {
final boolean supportsNestedRules = node.canBeViewedAs(MANAGED_INSTANCE_TYPE);
InputReferences inputs = transformedClosure.inputReferences();
List<InputReference> inputReferences = supportsNestedRules ? inputs.getOwnReferences() : inputs.getAllReferences();
final Map<String, PotentialInput> inputValues = Maps.newLinkedHashMap();
List<ModelReference<?>> inputModelReferences = Lists.newArrayList();
for (InputReference inputReference : inputReferences) {
String description = "@ line " + inputReference.getLineNumber();
String path = inputReference.getPath();
if (!inputValues.containsKey(path)) {
inputValues.put(path, new PotentialInput(inputModelReferences.size()));
inputModelReferences.add(ModelReference.untyped(ModelPath.path(path), description));
}
}
node.applyToSelf(role, InputUsingModelAction.of(ModelReference.of(node.getPath(), subjectType), descriptor, inputModelReferences, new BiAction<T, List<ModelView<?>>>() {
@Override
public void execute(T t, List<ModelView<?>> modelViews) {
// Make a copy of the closure, attach inputs and execute
Closure<?> cloned = closure.rehydrate(null, closure.getThisObject(), closure.getThisObject());
((TransformedClosure) cloned).makeRule(new PotentialInputs(modelViews, inputValues), supportsNestedRules ? ClosureBackedRuleFactory.this : null);
ClosureBackedAction.execute(t, cloned);
}
}));
}
};
}
private static class RelativePathSourceLocationTransformer implements Transformer<SourceLocation, TransformedClosure> {
private final RelativeFilePathResolver relativeFilePathResolver;
public RelativePathSourceLocationTransformer(RelativeFilePathResolver relativeFilePathResolver) {
this.relativeFilePathResolver = relativeFilePathResolver;
}
// TODO given that all the closures are from the same file, we should do the relativising once.
// that would entail adding location information to the model {} outer closure.
@Override
public SourceLocation transform(TransformedClosure closure) {
SourceLocation sourceLocation = closure.sourceLocation();
URI uri = sourceLocation.getUri();
String scheme = uri.getScheme();
String description;
if ("file".equalsIgnoreCase(scheme)) {
description = relativeFilePathResolver.resolveAsRelativePath(uri);
} else {
description = uri.toString();
}
return new SourceLocation(uri, description, sourceLocation.getExpression(), sourceLocation.getLineNumber(), sourceLocation.getColumnNumber());
}
}
}