Java tutorial
/* * 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.ide.common.repository.GradleCoordinate; import com.android.tools.idea.gradle.editor.entity.GradleEditorEntity; import com.android.tools.idea.gradle.editor.entity.GradleEditorEntityGroup; import com.android.tools.idea.gradle.editor.entity.VersionGradleEditorEntity; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.io.Files; import com.intellij.lang.ASTNode; import com.intellij.lang.LanguageParserDefinitions; import com.intellij.lang.ParserDefinition; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.util.GradleConstants; import org.jetbrains.plugins.groovy.GroovyLanguage; import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementVisitor; import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrApplicationStatement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import static com.android.tools.idea.gradle.editor.parser.GradleEditorModelParseContext.*; /** * Entry point for functionality of {@link #parse(VirtualFile, Project) building} {@link GradleEditorEntity} objects from * the target <code>build.gradle</code> file. */ public class GradleEditorModelParserFacade { private static final List<GradleEditorModelParser> ourParsers = Lists .<GradleEditorModelParser>newArrayList(new GradleEditorModelParserV1()); private static final Logger LOG = Logger.getInstance(GradleEditorModelParserFacade.class); @NotNull public List<GradleEditorEntityGroup> parse(@NotNull VirtualFile virtualFile, @NotNull Project project) { PsiManager psiManager = PsiManager.getInstance(project); PsiFile psiFile = psiManager.findFile(virtualFile); if (psiFile == null) { LOG.warn(String.format("Can't build PSI for the gradle config file '%s'", virtualFile.getCanonicalPath())); return Collections.emptyList(); } GradleEditorModelParseContext context = new GradleEditorModelParseContext(virtualFile, project); // This a two-steps process: // 1. Gradle config's PSI is parsed and the context is filled by assignments data; // 2. That data is given for further processing and actual building of model entities; fillContext(context, psiFile); for (VirtualFile dir = virtualFile.getParent(); dir != null; dir = dir.getParent()) { File settingsIoFile = new File(dir.getCanonicalPath(), GradleConstants.SETTINGS_FILE_NAME); if (!settingsIoFile.isFile()) { // Go up if there is no settings.gradle file in the current dir continue; } if (isParentProject(settingsIoFile, virtualFile)) { File parentIoFile = new File(dir.getCanonicalPath(), GradleConstants.DEFAULT_SCRIPT_NAME); VirtualFile parentVFile = LocalFileSystem.getInstance().findFileByIoFile(parentIoFile); if (parentVFile != null) { parentVFile.refresh(false, false); PsiFile parentPsiFile = psiManager.findFile(parentVFile); if (parentPsiFile != null) { context.onChangeFile(parentVFile); fillContext(context, parentPsiFile); } } } break; } return buildEntities(context); } private static boolean isParentProject(@NotNull File settingsFile, @NotNull VirtualFile targetConfigFile) { try { ImmutableList<String> lines = Files.asCharSource(settingsFile, Charset.forName("UTF-8")).readLines(); String startLineMarker = "include "; for (String line : lines) { if (!line.startsWith(startLineMarker)) { continue; } List<String> subProjects = Lists.newArrayList(); for (String s : Splitter.on(",").trimResults().omitEmptyStrings() .split(line.substring(startLineMarker.length()))) { // Sub-projects are defined as strings with leading colon, e.g. include ':app'. s = GradleEditorModelUtil.unquote(s); if (s.startsWith(":")) { s = s.substring(1); } subProjects.add(s); } List<String> dirs = Lists.newArrayList(); LocalFileSystem fileSystem = LocalFileSystem.getInstance(); VirtualFile rootDir = fileSystem.refreshAndFindFileByIoFile(settingsFile.getParentFile()); if (rootDir == null) { return false; } for (VirtualFile dir = targetConfigFile.getParent(); dir != null; dir = dir.getParent()) { if (rootDir.equals(dir)) { break; } dirs.add(dir.getName()); } Collections.reverse(dirs); int i = 0; for (String subProject : subProjects) { if (i >= dirs.size() || !subProject.equals(dirs.get(i++))) { return false; } } return true; } } catch (IOException e) { LOG.warn("Unexpected exception occurred on attempt to read contents of file " + settingsFile.getAbsolutePath()); } return false; } /** * Processes given PSI file and fills given context * by {@link GradleEditorModelParseContext#getAssignments(Variable) corresponding assignments}. * * @param context context to fill * @param psiFile psi file to parse */ private static void fillContext(@NotNull final GradleEditorModelParseContext context, @NotNull PsiFile psiFile) { psiFile.acceptChildren(new GroovyPsiElementVisitor(new GroovyRecursiveElementVisitor() { @Override public void visitMethodCallExpression(GrMethodCallExpression methodCallExpression) { Pair<String, TextRange> pair = GradleEditorValueExtractor.extractMethodName(methodCallExpression); GrClosableBlock[] closureArguments = methodCallExpression.getClosureArguments(); if (pair == null || closureArguments.length > 1) { super.visitMethodCallExpression(methodCallExpression); return; } if (closureArguments.length == 0) { if (methodCallExpression.getArgumentList().getAllArguments().length == 0) { // This is a no-args method, so, we just register it for cases like 'mavenCentral()' or 'jcenter()'. context.addCachedValue(NO_ARGS_METHOD_ASSIGNMENT_VALUE, TextRange.create( pair.second.getEndOffset(), methodCallExpression.getTextRange().getEndOffset())); context.registerAssignmentFromCachedData(pair.first, pair.second, methodCallExpression); } return; } context.onMethodEnter(pair.getFirst()); try { super.visitClosure(closureArguments[0]); } finally { context.onMethodExit(); } } @Override public void visitApplicationStatement(GrApplicationStatement applicationStatement) { Pair<String, TextRange> methodName = GradleEditorValueExtractor .extractMethodName(applicationStatement); if (methodName == null) { return; } GroovyPsiElement[] allArguments = applicationStatement.getArgumentList().getAllArguments(); if (allArguments.length == 1) { context.resetCaches(); extractValueOrVariable(allArguments[0], context); context.registerAssignmentFromCachedData(methodName.getFirst(), methodName.getSecond(), applicationStatement.getArgumentList()); } } @Override public void visitAssignmentExpression(GrAssignmentExpression expression) { // General idea is to try to extract variable from the given expression and, in case of success, try to extract rvalue and // register corresponding assignment with them. context.resetCaches(); extractValueOrVariable(expression.getLValue(), context); Multimap<Variable, Location> vars = context.getCachedVariables(); if (vars.size() != 1) { context.resetCaches(); return; } Map.Entry<Variable, Location> entry = vars.entries().iterator().next(); Variable lVariable = entry.getKey(); Location lVariableLocation = entry.getValue(); context.resetCaches(); GrExpression rValue = expression.getRValue(); if (rValue == null) { return; } extractValueOrVariable(rValue, context); if (context.getCachedValues().size() > 1) { Value value = new Value("", new Location(context.getCurrentFile(), GradleEditorModelUtil.interestedRange(rValue))); context.setCachedValues(Collections.singletonList(value)); } context.registerAssignmentFromCachedData(lVariable, lVariableLocation, rValue); context.resetCaches(); } @Override public void visitVariable(GrVariable variable) { TextRange nameRange = null; boolean lookForInitializer = false; ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE .findSingle(GroovyLanguage.INSTANCE); for (PsiElement e = variable.getFirstChild(); e != null; e = e.getNextSibling()) { ASTNode node = e.getNode(); if (node == null) { continue; } if (!lookForInitializer) { if (node.getElementType() == GroovyTokenTypes.mIDENT) { nameRange = e.getTextRange(); } else if (node.getElementType() == GroovyTokenTypes.mASSIGN) { if (nameRange == null) { return; } lookForInitializer = true; } continue; } if (node.getElementType() == GroovyTokenTypes.mNLS || node.getElementType() == GroovyTokenTypes.mSEMI) { break; } if (parserDefinition.getWhitespaceTokens().contains(node.getElementType())) { continue; } extractValueOrVariable(e, context); if (context.getCachedValues().size() > 1) { Value value = new Value("", new Location(context.getCurrentFile(), GradleEditorModelUtil.interestedRange(e))); context.setCachedValues(Collections.singletonList(value)); } if (context.registerAssignmentFromCachedData(variable.getName(), nameRange, e)) { return; } } } })); } /** * @see GradleEditorValueExtractor#extractValueOrVariable(PsiElement) */ private static void extractValueOrVariable(@NotNull PsiElement element, @NotNull final GradleEditorModelParseContext context) { new GradleEditorValueExtractor(context).extractValueOrVariable(element); } @NotNull private static List<GradleEditorEntityGroup> buildEntities(@NotNull GradleEditorModelParseContext context) { VersionGradleEditorEntity entity = GradleEditorModelParserV1.buildGradlePluginVersion(context); GradleCoordinate androidGradlePluginVersion = null; if (entity != null) { String currentVersion = entity.getCurrentValue(); if (!currentVersion.isEmpty()) { androidGradlePluginVersion = GradleCoordinate.parseVersionOnly(currentVersion); } } if (androidGradlePluginVersion == null) { androidGradlePluginVersion = GradleCoordinate.parseVersionOnly("0"); } Comparator<GradleCoordinate> c = GradleCoordinate.COMPARE_PLUS_HIGHER; for (GradleEditorModelParser parser : ourParsers) { if (c.compare(androidGradlePluginVersion, parser.getMinSupportedAndroidGradlePluginVersion()) >= 0 && c .compare(androidGradlePluginVersion, parser.getMaxSupportedAndroidGradlePluginVersion()) < 0) { return parser.buildEntities(context); } } return Collections.emptyList(); } }