/*
* Copyright 2016 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.platform.base.internal.registry;
import com.google.common.collect.ImmutableList;
import org.gradle.api.Nullable;
import org.gradle.language.base.LanguageSourceSet;
import org.gradle.language.base.plugins.ComponentModelBasePlugin;
import org.gradle.language.base.plugins.LanguageBasePlugin;
import org.gradle.model.InvalidModelRuleDeclarationException;
import org.gradle.model.internal.core.ModelActionRole;
import org.gradle.model.internal.core.ModelReference;
import org.gradle.model.internal.core.ModelView;
import org.gradle.model.internal.core.MutableModelNode;
import org.gradle.model.internal.inspect.*;
import org.gradle.model.internal.manage.schema.ModelSchema;
import org.gradle.model.internal.manage.schema.ModelSchemaStore;
import org.gradle.model.internal.type.ModelType;
import org.gradle.platform.base.*;
import org.gradle.platform.base.component.internal.ComponentSpecFactory;
import org.gradle.platform.base.internal.builder.TypeBuilderInternal;
import org.gradle.platform.base.plugins.BinaryBasePlugin;
import org.gradle.platform.base.plugins.ComponentBasePlugin;
import java.util.List;
import static java.util.Collections.emptyList;
public class ComponentTypeModelRuleExtractor extends AbstractAnnotationDrivenComponentModelRuleExtractor<ComponentType> {
public static final ModelType<ComponentSpecFactory> COMPONENT_SPEC_FACTORY_CLASS = ModelType.of(ComponentSpecFactory.class);
private static final ModelReference<ComponentSpecFactory> COMPONENT_SPEC_FACTORY_MODEL_REFERENCE = ModelReference.of(COMPONENT_SPEC_FACTORY_CLASS);
private static final ModelType<ComponentSpec> COMPONENT_SPEC_MODEL_TYPE = ModelType.of(ComponentSpec.class);
private static final ModelType<LanguageSourceSet> LANGUAGE_SOURCE_SET_MODEL_TYPE = ModelType.of(LanguageSourceSet.class);
private static final ModelType<BinarySpec> BINARY_SPEC_MODEL_TYPE = ModelType.of(BinarySpec.class);
private static final ModelType<SourceComponentSpec> SOURCE_COMPONENT_SPEC_MODEL_TYPE = ModelType.of(SourceComponentSpec.class);
private static final ModelType<VariantComponentSpec> VARIANT_COMPONENT_SPEC_MODEL_TYPE = ModelType.of(VariantComponentSpec.class);
private static class ComponentTypeRegistrationInfo {
private final String modelName;
private final ModelType<? extends ComponentSpec> baseInterface;
private final List<Class<?>> requiredPlugins;
private ComponentTypeRegistrationInfo(String modelName, ModelType<? extends ComponentSpec> baseInterface, Class<?> requiredPlugins) {
this.modelName = modelName;
this.baseInterface = baseInterface;
this.requiredPlugins = ImmutableList.<Class<?>>of(requiredPlugins);
}
}
private final ModelSchemaStore schemaStore;
private final ModelReference<ComponentSpecFactory> registryRef;
public ComponentTypeModelRuleExtractor(ModelSchemaStore schemaStore) {
this.schemaStore = schemaStore;
this.registryRef = COMPONENT_SPEC_FACTORY_MODEL_REFERENCE;
}
@Nullable
@Override
public <R, S> ExtractedModelRule registration(MethodRuleDefinition<R, S> ruleDefinition, MethodModelRuleExtractionContext context) {
validateIsVoidMethod(ruleDefinition, context);
if (ruleDefinition.getReferences().size() != 1) {
context.add(ruleDefinition, String.format("A method %s must have a single parameter of type %s.", getDescription(), TypeBuilder.class.getName()));
return null;
}
ModelReference<?> subjectReference = ruleDefinition.getSubjectReference();
ModelType<?> subjectType = subjectReference.getType();
Class<?> rawSubjectType = subjectType.getRawClass();
if (!rawSubjectType.equals(TypeBuilder.class)) {
context.add(ruleDefinition, String.format("A method %s must have a single parameter of type %s.", getDescription(), TypeBuilder.class.getName()));
return null;
}
List<ModelType<?>> typeVariables = subjectType.getTypeVariables();
if (typeVariables.size() != 1) {
context.add(ruleDefinition, String.format("Parameter of type %s must declare a type parameter.", rawSubjectType.getName()));
return null;
}
ModelType<?> builtType = typeVariables.get(0);
if (builtType.isWildcard()) {
context.add(ruleDefinition, String.format("Type '%s' cannot be a wildcard type (i.e. cannot use ? super, ? extends etc.).", builtType.toString()));
return null;
}
ComponentTypeRegistrationInfo info = componentTypeRegistrationInfoFor(builtType);
if (info == null) {
context.add(ruleDefinition, String.format("Type '%s' is not a subtype of '%s'.", builtType.toString(), COMPONENT_SPEC_MODEL_TYPE.toString()));
return null;
}
return new ExtractedTypeRule(ruleDefinition, info, builtType.asSubtype(info.baseInterface));
}
private ComponentTypeRegistrationInfo componentTypeRegistrationInfoFor(ModelType<?> builtType) {
if (LANGUAGE_SOURCE_SET_MODEL_TYPE.isAssignableFrom(builtType)) {
return new ComponentTypeRegistrationInfo("language", LANGUAGE_SOURCE_SET_MODEL_TYPE, LanguageBasePlugin.class);
}
if (BINARY_SPEC_MODEL_TYPE.isAssignableFrom(builtType)) {
return new ComponentTypeRegistrationInfo("binary", BINARY_SPEC_MODEL_TYPE, BinaryBasePlugin.class);
}
if (COMPONENT_SPEC_MODEL_TYPE.isAssignableFrom(builtType)) {
Class<?> requiredPlugin = SOURCE_COMPONENT_SPEC_MODEL_TYPE.isAssignableFrom(builtType) || VARIANT_COMPONENT_SPEC_MODEL_TYPE.isAssignableFrom(builtType)
? ComponentModelBasePlugin.class
: ComponentBasePlugin.class;
return new ComponentTypeRegistrationInfo("component", COMPONENT_SPEC_MODEL_TYPE, requiredPlugin);
}
return null;
}
private ModelType<?> determineImplementationType(TypeBuilderInternal<?> builder) {
validateInternalViewsAreInterfaces(builder);
Class<?> implementation = builder.getDefaultImplementation();
if (implementation == null) {
return null;
}
return ModelType.of(implementation);
}
private void validateInternalViewsAreInterfaces(TypeBuilderInternal<?> builder) {
for (Class<?> internalView : builder.getInternalViews()) {
if (!internalView.isInterface()) {
throw new InvalidModelException(String.format("Internal view %s must be an interface.", internalView.getName()));
}
}
}
private class ExtractedTypeRule extends AbstractExtractedModelRule {
private final ComponentTypeRegistrationInfo info;
private final ModelType<? extends ComponentSpec> modelType;
public ExtractedTypeRule(MethodRuleDefinition<?, ?> ruleDefinition, ComponentTypeRegistrationInfo info, ModelType<? extends ComponentSpec> modelType) {
super(ruleDefinition);
this.info = info;
this.modelType = modelType;
}
@Override
public List<? extends Class<?>> getRuleDependencies() {
return info.requiredPlugins;
}
@Override
public void apply(final MethodModelRuleApplicationContext context, MutableModelNode target) {
context.getRegistry().configure(
ModelActionRole.Mutate,
context.contextualize(new TypeRegistrationAction()));
}
private class TypeRegistrationAction extends AbstractMethodRuleAction<ComponentSpecFactory> {
public TypeRegistrationAction() {
super(registryRef, getRuleDefinition().getDescriptor());
}
@Override
public List<? extends ModelReference<?>> getInputs() {
return emptyList();
}
@Override
protected void execute(ModelRuleInvoker<?> invoker, ComponentSpecFactory registry, List<ModelView<?>> inputs) {
try {
ModelSchema<? extends ComponentSpec> schema = schemaStore.getSchema(modelType);
TypeBuilderInternal<? extends ComponentSpec> builder = new DefaultTypeBuilder<ComponentSpec>(getAnnotationType(), schema);
invoker.invoke(builder);
ModelType<?> implModelType = determineImplementationType(builder);
registry.register(modelType, builder.getInternalViews(), implModelType, getDescriptor());
} catch (InvalidModelException e) {
throw invalidModelRule(getRuleDefinition(), info.modelName, e);
}
}
private InvalidModelRuleDeclarationException invalidModelRule(MethodRuleDefinition<?, ?> ruleDefinition, String modelName, InvalidModelException e) {
StringBuilder sb = new StringBuilder();
ruleDefinition.getDescriptor().describeTo(sb);
sb.append(String.format(" is not a valid %s model rule method.", modelName));
return new InvalidModelRuleDeclarationException(sb.toString(), e);
}
}
}
}