blob: b19946b12045130d71d2d921bea90962acbc0af4 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 com.android.tools.idea.gradle.editor.parser;
import com.android.SdkConstants;
import com.android.ide.common.repository.GradleCoordinate;
import com.android.tools.idea.gradle.editor.entity.*;
import com.android.tools.idea.gradle.editor.metadata.GradleEditorEntityMetaData;
import com.android.tools.idea.gradle.editor.metadata.StdGradleEditorEntityMetaData;
import com.android.tools.idea.gradle.editor.value.BuildToolsValueManager;
import com.android.tools.idea.gradle.editor.value.LibraryVersionsManager;
import com.android.tools.idea.gradle.editor.value.SdkValueManager;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.openapi.diagnostic.Logger;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.android.tools.idea.gradle.editor.parser.GradleEditorDsl.*;
import static com.android.tools.idea.gradle.editor.parser.GradleEditorModelParseContext.*;
import static com.android.tools.idea.gradle.editor.parser.GradleEditorModelUtil.buildSourceBinding;
public class GradleEditorModelParserV1 implements GradleEditorModelParser {
private static final Logger LOG = Logger.getInstance(GradleEditorModelParserV1.class);
/**
* Qualifier for a top-level variable.
*/
private static final List<String> PROJECT_QUALIFIER = Collections.emptyList();
@NotNull
@Override
public GradleCoordinate getMinSupportedAndroidGradlePluginVersion() {
return GradleCoordinate.parseVersionOnly("0");
}
@NotNull
@Override
public GradleCoordinate getMaxSupportedAndroidGradlePluginVersion() {
return GradleCoordinate.parseVersionOnly("100");
}
@NotNull
@Override
public List<GradleEditorEntityGroup> buildEntities(@NotNull GradleEditorModelParseContext context) {
List<GradleEditorEntityGroup> result = Lists.newArrayList();
GradleEditorEntityGroup buildConfiguration = buildConfiguration(context);
if (buildConfiguration != null) {
result.add(buildConfiguration);
}
GradleEditorEntityGroup dependencies = dependencies(context);
if (dependencies != null) {
result.add(dependencies);
}
GradleEditorEntityGroup repositories = repositories(context);
if (repositories != null) {
result.add(repositories);
}
for (GradleEditorModelCustomizer customizer : GradleEditorModelCustomizer.EP_NAME.getExtensions()) {
result = customizer.postProcess(result, context);
}
return result;
}
@Nullable
protected static GradleEditorEntityGroup combine(@NotNull String sectionText, @NotNull GradleEditorEntity... childEntities) {
GradleEditorEntityGroup result = null;
for (GradleEditorEntity childEntity : childEntities) {
if (childEntity == null) {
continue;
}
if (result == null) {
result = new GradleEditorEntityGroup(sectionText);
}
result.addEntity(childEntity);
}
return result;
}
@Nullable
protected GradleEditorEntityGroup buildConfiguration(@NotNull GradleEditorModelParseContext context) {
return combine(AndroidBundle.message("android.gradle.editor.header.build"), buildGradlePluginVersion(context),
buildCompileSdkVersion(context), buildBuildSdkVersion(context));
}
@Nullable
public static VersionGradleEditorEntity buildGradlePluginVersion(@NotNull final GradleEditorModelParseContext context) {
final Variable variable = new Variable(CLASSPATH_CONFIGURATION, PROJECT_QUALIFIER);
GradleEditorDependencyParser dependencyParser = new GradleEditorDependencyParser();
for (Assignment assignment : context.getAssignments(variable)) {
GradleEditorEntity entity = dependencyParser.parse(assignment, context);
if (!(entity instanceof ExternalDependencyGradleEditorEntity)) {
continue;
}
ExternalDependencyGradleEditorEntity e = (ExternalDependencyGradleEditorEntity)entity;
int i = SdkConstants.GRADLE_PLUGIN_NAME.indexOf(':');
String groupId = SdkConstants.GRADLE_PLUGIN_NAME.substring(0, i);
String artifactId = SdkConstants.GRADLE_PLUGIN_NAME.substring(i + 1);
if (artifactId.endsWith(":")) {
artifactId = artifactId.substring(0, artifactId.length() - 1);
}
if (!groupId.equals(e.getGroupId()) || !artifactId.equals(e.getArtifactId())) {
continue;
}
final Set<GradleEditorEntityMetaData> metaData = Sets.newHashSet(e.getMetaData());
if (!e.getMetaData().contains(StdGradleEditorEntityMetaData.INJECTED)) {
// We consider that gradle plugin entity is injected if it's declared in a parent file.
metaData.add(StdGradleEditorEntityMetaData.OUTGOING);
}
metaData.remove(StdGradleEditorEntityMetaData.REMOVABLE);
return new VersionGradleEditorEntity(AndroidBundle.message("android.gradle.editor.version.gradle.plugin"),
e.getVersionSourceBindings(), e.getEntityLocation(), metaData, e.getDeclarationValueLocation(),
e.getVersion(), new LibraryVersionsManager(groupId, artifactId),
"http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Simple-build-files");
}
return null;
}
@Nullable
protected GradleEditorEntity buildCompileSdkVersion(@NotNull GradleEditorModelParseContext context) {
SimpleEntityData entityData = buildSimpleEntityData(new Variable(COMPILE_SDK_VERSION, PROJECT_QUALIFIER), context, null);
if (entityData == null) {
return null;
}
return new VersionGradleEditorEntity(AndroidBundle.message("android.gradle.editor.version.sdk.compile"), entityData.definitionValueSourceBindings,
entityData.wholeEntityLocation, entityData.metaData, entityData.declarationValueLocation,
entityData.value, new SdkValueManager(), null);
}
@Nullable
protected GradleEditorEntity buildBuildSdkVersion(@NotNull GradleEditorModelParseContext context) {
SimpleEntityData entityData = buildSimpleEntityData(new Variable(BUILD_TOOLS_VERSION, PROJECT_QUALIFIER), context, null);
if (entityData == null) {
return null;
}
return new VersionGradleEditorEntity(AndroidBundle.message("android.gradle.editor.version.build.tools"), entityData.definitionValueSourceBindings,
entityData.wholeEntityLocation, entityData.metaData, entityData.declarationValueLocation,
entityData.value, new BuildToolsValueManager(), null);
}
/**
* Enhances {@link GradleEditorModelUtil#collectInfo(Collection, GradleEditorModelParseContext, GradleEditorModelUtil.AssignmentFilter)}
* in a way to add meta-data to it.
*
* @param lValue target lvalue
* @param context current context
* @param assignmentFilter assignment filter to use
* @return target entity data holder if the data is successfully extracted; <code>null</code> otherwise
*/
@Nullable
protected static SimpleEntityData buildSimpleEntityData(@NotNull Variable lValue,
@NotNull GradleEditorModelParseContext context,
@Nullable GradleEditorModelUtil.AssignmentFilter assignmentFilter) {
Assignment targetAssignment = null;
for (Assignment assignment : context.getAssignments(lValue)) {
Assignment assignmentToUse = assignmentFilter == null ? assignment : assignmentFilter.check(assignment);
if (assignmentToUse == null) {
continue;
}
if (targetAssignment == null) {
targetAssignment = assignmentToUse;
}
else {
LOG.warn(String
.format("More than one assignment for the same l-value (%s) is found at the target file (%s): '%s' and '%s',", lValue,
context.getTargetFile().getPath(), targetAssignment, assignmentToUse));
return null;
}
}
if (targetAssignment == null) {
return null;
}
GradleEditorSourceBinding declarationValueLocation = buildSourceBinding(targetAssignment.rValueLocation, context.getProject());
if (declarationValueLocation == null) {
return null;
}
GradleEditorSourceBinding wholeEntityLocation = buildSourceBinding(targetAssignment, context.getProject());
if (wholeEntityLocation == null) {
return null;
}
GradleEditorModelUtil.EntityInfo entityInfo =
GradleEditorModelUtil.collectInfo(Collections.singleton(lValue), context, assignmentFilter);
if (entityInfo.sourceBindings.isEmpty()) {
return null;
}
Set<GradleEditorEntityMetaData> metaData = buildMetaData(targetAssignment, context);
return new SimpleEntityData(entityInfo.sourceBindings, metaData, entityInfo.value, wholeEntityLocation, declarationValueLocation);
}
@Nullable
protected static SimpleEntityData buildSimpleEntityData(@NotNull Assignment assignment, @NotNull GradleEditorModelParseContext context) {
GradleEditorSourceBinding declarationValueLocation = buildSourceBinding(assignment.rValueLocation, context.getProject());
if (declarationValueLocation == null) {
return null;
}
GradleEditorSourceBinding wholeEntityLocation = buildSourceBinding(assignment, context.getProject());
if (wholeEntityLocation == null) {
return null;
}
Set<GradleEditorEntityMetaData> metaData = buildMetaData(assignment, context);
if (assignment.dependencies.isEmpty()) {
return new SimpleEntityData(Collections.singleton(declarationValueLocation), metaData,
assignment.value == null ? "" : assignment.value.value, wholeEntityLocation, declarationValueLocation);
}
GradleEditorModelUtil.EntityInfo info = GradleEditorModelUtil.collectInfo(assignment.dependencies.asMap().keySet(), context, null);
List<GradleEditorSourceBinding> definitionValueSourceBindings = Lists.newArrayList(info.sourceBindings);
if (assignment.value != null && assignment.value.value.isEmpty()) {
definitionValueSourceBindings.add(declarationValueLocation);
}
String valueToUse = assignment.value == null ? info.value : assignment.value.value;
return new SimpleEntityData(definitionValueSourceBindings, metaData, valueToUse , wholeEntityLocation, declarationValueLocation);
}
@NotNull
protected static Set<GradleEditorEntityMetaData> buildMetaData(@NotNull Assignment assignment,
@NotNull GradleEditorModelParseContext context) {
Set<GradleEditorEntityMetaData> result = Sets.newHashSet();
if (context.getTargetFile().equals(assignment.lValueLocation.file)) {
if (!assignment.codeStructure.isEmpty()) {
String topSection = assignment.codeStructure.get(0);
if (ALL_PROJECTS_SECTION.equals(topSection) || SUB_PROJECT_SECTION.equals(topSection)) {
result.add(StdGradleEditorEntityMetaData.OUTGOING);
}
}
}
else {
result.add(StdGradleEditorEntityMetaData.INJECTED);
}
return result;
}
@Nullable
protected GradleEditorEntityGroup dependencies(@NotNull GradleEditorModelParseContext context) {
List<GradleEditorEntity> dependencies = Lists.newArrayList();
GradleEditorDependencyParser dependencyParser = new GradleEditorDependencyParser();
Collection<Assignment> currentDependencies = context.getAssignments(Collections.singletonList(DEPENDENCIES_SECTION));
Collection<Assignment> subProjectDependencies = context.getAssignments(Lists.newArrayList(SUB_PROJECT_SECTION, DEPENDENCIES_SECTION));
Collection<Assignment> allProjectDependencies = context.getAssignments(Lists.newArrayList(ALL_PROJECTS_SECTION, DEPENDENCIES_SECTION));
for (Assignment assignment : Iterables.concat(currentDependencies, subProjectDependencies, allProjectDependencies)) {
GradleEditorEntity entity = dependencyParser.parse(assignment, context);
if (entity != null) {
dependencies.add(entity);
}
}
if (dependencies.isEmpty()) {
return null;
}
String headerText = AndroidBundle.message("android.gradle.editor.header.dependencies");
return combine(headerText, Iterables.toArray(dependencies, GradleEditorEntity.class));
}
@Nullable
protected GradleEditorEntityGroup repositories(@NotNull GradleEditorModelParseContext context) {
List<GradleEditorEntity> repositories = Lists.newArrayList();
Collection<List<String>> requestKeys = Lists.newArrayList();
requestKeys.add(Arrays.asList(BUILD_SCRIPT_SECTION, REPOSITORIES_SECTION)); // Current public repo
requestKeys.add(Arrays.asList(BUILD_SCRIPT_SECTION, REPOSITORIES_SECTION, MAVEN_REPO)); // Current third-party repo
requestKeys.add(Arrays.asList(SUB_PROJECT_SECTION, REPOSITORIES_SECTION)); // Outgoing public repo
requestKeys.add(Arrays.asList(SUB_PROJECT_SECTION, REPOSITORIES_SECTION, MAVEN_REPO)); // Outgoing third-party repo
requestKeys.add(Arrays.asList(ALL_PROJECTS_SECTION, REPOSITORIES_SECTION)); // 'All-projects' public repo
requestKeys.add(Arrays.asList(ALL_PROJECTS_SECTION, REPOSITORIES_SECTION, MAVEN_REPO)); // 'All-projects' third-party repo
Collection<Assignment> assignments = Lists.newArrayList();
for (List<String> key : requestKeys) {
assignments.addAll(context.getAssignments(key));
}
for (Assignment assignment : assignments) {
if (assignment.value == null) {
continue;
}
if (!context.getTargetFile().equals(assignment.lValueLocation.file) && !assignment.codeStructure.isEmpty()) {
String topSection = assignment.codeStructure.get(0);
if (!ALL_PROJECTS_SECTION.equals(topSection) && !SUB_PROJECT_SECTION.equals(topSection)) {
// Repositories declared at the parent's 'buildscript' section are not visible to the child projects.
continue;
}
}
if (NO_ARGS_METHOD_ASSIGNMENT_VALUE.equals(assignment.value.value)) {
String name = null;
String value = null;
String helpId = null;
if (MAVEN_CENTRAL.equals(assignment.lValue.name)) {
name = AndroidBundle.message("android.gradle.editor.header.repository.maven");
value = GradleEditorRepositoryEntity.MAVEN_CENTRAL_URL;
helpId = GradleEditorRepositoryEntity.MAVEN_CENTRAL_HELP_ID;
}
else if (JCENTER.equals(assignment.lValue.name)) {
name = AndroidBundle.message("android.gradle.editor.header.repository.jcenter");
value = GradleEditorRepositoryEntity.JCENTER_URL;
helpId = GradleEditorRepositoryEntity.JCENTER_HELP_ID;
}
if (name != null) {
Set<GradleEditorEntityMetaData> metaData = buildMetaData(assignment, context);
metaData.add(StdGradleEditorEntityMetaData.READ_ONLY);
GradleEditorSourceBinding sourceBinding = buildSourceBinding(assignment, context.getProject());
if (sourceBinding != null) {
repositories.add(
new GradleEditorRepositoryEntity(name, value, Collections.<GradleEditorSourceBinding>emptyList(), sourceBinding, metaData,
sourceBinding, helpId));
}
}
continue;
}
if (!MAVEN_REPO_URL.equals(assignment.lValue.name)) {
continue;
}
SimpleEntityData data = buildSimpleEntityData(assignment, context);
if (data != null) {
repositories.add(
new GradleEditorRepositoryEntity(AndroidBundle.message("android.gradle.editor.header.repository.third.party"), data.value,
data.definitionValueSourceBindings, data.wholeEntityLocation, data.metaData, data.declarationValueLocation,
GradleEditorRepositoryEntity.MAVEN_GENERIC_HELP_ID));
}
}
if (repositories.isEmpty()) {
return null;
}
String headerText = AndroidBundle.message("android.gradle.editor.header.repositories");
return combine(headerText, Iterables.toArray(repositories, GradleEditorEntity.class));
}
/**
* Parameter object used to hold intermediate data for building {@link GradleEditorEntity} object.
*/
private static class SimpleEntityData {
@NotNull final Collection<GradleEditorSourceBinding> definitionValueSourceBindings;
@NotNull final Set<GradleEditorEntityMetaData> metaData;
@NotNull final String value;
@NotNull final GradleEditorSourceBinding wholeEntityLocation;
@NotNull final GradleEditorSourceBinding declarationValueLocation;
SimpleEntityData(@NotNull Collection<GradleEditorSourceBinding> definitionValueSourceBindings,
@NotNull Set<GradleEditorEntityMetaData> metaData,
@NotNull String value,
@NotNull GradleEditorSourceBinding wholeEntityLocation,
@NotNull GradleEditorSourceBinding declarationValueLocation) {
this.definitionValueSourceBindings = definitionValueSourceBindings;
this.metaData = metaData;
this.value = value;
this.wholeEntityLocation = wholeEntityLocation;
this.declarationValueLocation = declarationValueLocation;
}
}
}