blob: 240ff57cfa807a2fe1eecf2bbbd8cb70660eb272 [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.dsl.parser;
import com.android.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import java.util.List;
import java.util.Map;
import static com.android.tools.idea.gradle.dsl.parser.PsiElements.getUnquotedText;
import static com.google.common.base.Strings.emptyToNull;
import static com.intellij.openapi.util.text.StringUtil.isNotEmpty;
import static com.intellij.psi.util.PsiTreeUtil.getChildOfType;
/**
* A Gradle external dependency. There are two notations supported for declaring a dependency on an external module. One is a string
* notation formatted this way:
* <pre>
* configurationName "group:name:version:classifier@extension"
* </pre>
* The other is a map notation:
* <pre>
* configurationName group: group:, name: name, version: version, classifier: classifier, ext: extension
* </pre>
* For more details, visit:
* <ol>
* <li><a href="https://docs.gradle.org/2.4/userguide/dependency_management.html">Gradle Dependency Management</a></li>
* <li><a href="https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html">Gradle
* DependencyHandler</a></li>
* </ol>
*/
public class ExternalDependencyElement implements DependencyElement {
@NotNull private final String myConfigurationName;
@NotNull private final Notation myNotation;
@Nullable
static ExternalDependencyElement withCompactNotation(@NotNull String configurationName, @NotNull GrLiteral literal) {
Notation notation = CompactNotation.parse(literal);
if (notation != null) {
return new ExternalDependencyElement(configurationName, notation);
}
return null;
}
@Nullable
static ExternalDependencyElement withMapNotation(@NotNull String configurationName, @NotNull List<GrNamedArgument> namedArguments) {
Notation notation = MapNotation.parse(namedArguments);
if (notation != null) {
return new ExternalDependencyElement(configurationName, notation);
}
return null;
}
private ExternalDependencyElement(@NotNull String configurationName, @NotNull Notation notation) {
myConfigurationName = configurationName;
myNotation = notation;
}
@NotNull
public String getConfigurationName() {
return myConfigurationName;
}
@Nullable
public String getGroup() {
return myNotation.getSpec().group;
}
@NotNull
public String getName() {
return myNotation.getSpec().name;
}
@Nullable
public String getVersion() {
return myNotation.getSpec().version;
}
public void setVersion(@NotNull String version) {
ApplicationManager.getApplication().assertWriteAccessAllowed();
myNotation.setVersion(version);
}
@Nullable
public String getClassifier() {
return myNotation.getSpec().classifier;
}
@Nullable
public String getExtension() {
return myNotation.getSpec().extension;
}
@Override
public String toString() {
return "ExternalDependencyElement{" +
"configurationName='" + myConfigurationName + '\'' +
", spec='" + myNotation.getSpec() + '\'' +
'}';
}
private interface Notation {
@NotNull
DependencySpec getSpec();
void setVersion(@NotNull String version);
}
@VisibleForTesting
static class CompactNotation implements Notation {
@NotNull private final GrLiteral myLiteral;
@NotNull private final DependencySpec mySpec;
@Nullable
static CompactNotation parse(@NotNull GrLiteral literal) {
String text = getUnquotedText(literal);
if (text != null) {
DependencySpec spec = parse(text);
if (spec != null) {
return new CompactNotation(literal, spec);
}
}
return null;
}
@VisibleForTesting
@Nullable
static DependencySpec parse(@NotNull String text) {
// Example: org.gradle.test.classifiers:service:1.0:jdk15@jar where
// group: org.gradle.test.classifiers
// name: service
// version: 1.0
// classifier: jdk15
// extension: jar
List<String> segments = Splitter.on(':').trimResults().omitEmptyStrings().splitToList(text);
int segmentCount = segments.size();
if (segmentCount > 0) {
segments = Lists.newArrayList(segments);
String lastSegment = segments.remove(segmentCount - 1);
String extension = null;
int indexOfAt = lastSegment.indexOf('@');
if (indexOfAt != -1) {
extension = lastSegment.substring(indexOfAt + 1, lastSegment.length());
lastSegment = lastSegment.substring(0, indexOfAt);
}
segments.add(lastSegment);
segmentCount = segments.size();
String group = null;
String name = null;
String version = null;
String classifier = null;
if (segmentCount == 1) {
name = segments.get(0);
}
else if (segmentCount == 2) {
if (!lastSegment.isEmpty() && Character.isDigit(lastSegment.charAt(0))) {
name = segments.get(0);
version = lastSegment;
}
else {
group = segments.get(0);
name = segments.get(1);
}
}
else if (segmentCount == 3 || segmentCount == 4) {
group = segments.get(0);
name = segments.get(1);
version = segments.get(2);
if (segmentCount == 4) {
classifier = segments.get(3);
}
}
if (isNotEmpty(name)) {
return new DependencySpec(name, group, version, classifier, extension);
}
}
return null;
}
private CompactNotation(@NotNull GrLiteral literal, @NotNull DependencySpec spec) {
myLiteral = literal;
mySpec = spec;
}
@Override
@NotNull
public DependencySpec getSpec() {
return mySpec;
}
@Override
public void setVersion(@NotNull String version) {
Project project = myLiteral.getProject();
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(project);
mySpec.version = version;
GrLiteral newCoordinatePsiLiteral = factory.createLiteralFromValue(mySpec.toString());
myLiteral.replace(newCoordinatePsiLiteral);
}
}
@VisibleForTesting
static class MapNotation implements Notation {
@NonNls private static final String VERSION_PROPERTY = "version";
@NotNull private final Map<String, GrLiteral> myArgumentsByName;
@NotNull private final DependencySpec mySpec;
@Nullable
static MapNotation parse(@NotNull List<GrNamedArgument> namedArguments) {
Map<String, GrLiteral> argumentsByName = Maps.newHashMap();
Map<String, String> argumentValuesByName = Maps.newHashMap();
for (GrNamedArgument argument : namedArguments) {
GrLiteral literal = getChildOfType(argument, GrLiteral.class);
if (literal != null) {
String name = argument.getLabelName();
argumentsByName.put(name, literal);
argumentValuesByName.put(name, getUnquotedText(literal));
}
}
DependencySpec spec = parse(argumentValuesByName);
if (spec != null) {
return new MapNotation(argumentsByName, spec);
}
return null;
}
@VisibleForTesting
@Nullable
static DependencySpec parse(@NotNull Map<String, String> namedArguments) {
String name = namedArguments.get("name");
if (isNotEmpty(name)) {
return new DependencySpec(name, namedArguments.get("group"), namedArguments.get(VERSION_PROPERTY), namedArguments.get("classifier"),
namedArguments.get("ext"));
}
return null;
}
private MapNotation(@NotNull Map<String, GrLiteral> argumentsByName, @NotNull DependencySpec spec) {
myArgumentsByName = argumentsByName;
mySpec = spec;
}
@Override
@NotNull
public DependencySpec getSpec() {
return mySpec;
}
@Override
public void setVersion(@NotNull String version) {
GrLiteral literal = myArgumentsByName.get(VERSION_PROPERTY);
if (literal != null) {
Project project = literal.getProject();
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(project);
mySpec.version = version;
GrLiteral newCoordinatePsiLiteral = factory.createLiteralFromValue(version);
literal.replace(newCoordinatePsiLiteral);
}
// TODO handle case where 'version' property is not defined, and needs to be added.
}
}
@VisibleForTesting
static class DependencySpec {
@NotNull String name;
@Nullable String group;
@Nullable String version;
@Nullable String classifier;
@Nullable String extension;
@VisibleForTesting
DependencySpec(@NotNull String name,
@Nullable String group,
@Nullable String version,
@Nullable String classifier,
@Nullable String extension) {
this.name = name;
this.group = emptyToNull(group);
this.version = emptyToNull(version);
this.classifier = emptyToNull(classifier);
this.extension = emptyToNull(extension);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DependencySpec that = (DependencySpec)o;
return Objects.equal(name, that.name) &&
Objects.equal(group, that.group) &&
Objects.equal(version, that.version) &&
Objects.equal(classifier, that.classifier) &&
Objects.equal(extension, that.extension);
}
@Override
public int hashCode() {
return Objects.hashCode(name, group, version, classifier, extension);
}
@Override
public String toString() {
List<String> segments = Lists.newArrayList(group, name, version, classifier);
String s = Joiner.on(':').skipNulls().join(segments);
if (extension != null) {
s += "@" + extension;
}
return s;
}
}
}