1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.currysrc.api.process.ast;
17 
18 import com.google.common.base.Splitter;
19 import com.google.common.collect.ImmutableList;
20 
21 import java.io.IOException;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import org.eclipse.jdt.core.dom.ASTNode;
25 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
26 import org.eclipse.jdt.core.dom.BodyDeclaration;
27 import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
28 import org.eclipse.jdt.core.dom.FieldDeclaration;
29 import org.eclipse.jdt.core.dom.MethodDeclaration;
30 
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 
35 /**
36  * Utility methods associated with {@link BodyDeclarationLocator} and its standard implementations.
37  */
38 public final class BodyDeclarationLocators {
39 
BodyDeclarationLocators()40   private BodyDeclarationLocators() {
41   }
42 
createLocatorsFromStrings(String[] locatorStrings)43   public static List<BodyDeclarationLocator> createLocatorsFromStrings(String[] locatorStrings) {
44     ImmutableList.Builder<BodyDeclarationLocator> locatorListBuilder = ImmutableList.builder();
45     for (String locatorString : locatorStrings) {
46       BodyDeclarationLocator locator = BodyDeclarationLocators.fromStringForm(locatorString);
47       locatorListBuilder.add(locator);
48     }
49     return locatorListBuilder.build();
50   }
51 
52   /**
53    * Generates strings that can be used with {@link #fromStringForm(String)} to generate
54    * {@link BodyDeclarationLocator} instances capable of locating the supplied node. Usually returns
55    * a single element, except for fields declarations.
56    */
toLocatorStringForms(BodyDeclaration bodyDeclaration)57   public static List<String> toLocatorStringForms(BodyDeclaration bodyDeclaration) {
58     List<BodyDeclarationLocator> locators = createLocators(bodyDeclaration);
59     List<String> stringForms = new ArrayList<>(locators.size());
60     for (BodyDeclarationLocator locator : locators) {
61       stringForms.add(locator.getStringFormType() + ":" + locator.getStringFormTarget());
62     }
63     return stringForms;
64   }
65 
66   /**
67    * Creates {@link BodyDeclarationLocator} objects that can find the supplied
68    * {@link BodyDeclaration}. Usually returns a single element, except for fields declarations.
69    */
createLocators(BodyDeclaration bodyDeclaration)70   public static List<BodyDeclarationLocator> createLocators(BodyDeclaration bodyDeclaration) {
71     AbstractTypeDeclaration typeDeclaration = TypeLocator.findTypeDeclarationNode(bodyDeclaration);
72     if (typeDeclaration == null) {
73       throw new AssertionError("Unable to find type declaration for " + typeDeclaration);
74     }
75     TypeLocator typeLocator = new TypeLocator(typeDeclaration);
76 
77     int nodeType = bodyDeclaration.getNodeType();
78     switch (nodeType) {
79       case ASTNode.FIELD_DECLARATION:
80         List<String> fieldNames = FieldLocator.getFieldNames((FieldDeclaration) bodyDeclaration);
81         List<BodyDeclarationLocator> fieldLocators = new ArrayList<>(fieldNames.size());
82         for (String fieldName : fieldNames) {
83           fieldLocators.add(new FieldLocator(typeLocator, fieldName));
84         }
85         return fieldLocators;
86       case ASTNode.METHOD_DECLARATION:
87         MethodDeclaration methodDeclaration = (MethodDeclaration) bodyDeclaration;
88         List<String> parameterTypeNames = ParameterMatcher.getParameterTypeNames(methodDeclaration);
89         return ImmutableList.<BodyDeclarationLocator>of(
90             new MethodLocator(typeLocator, methodDeclaration.getName().getIdentifier(),
91             parameterTypeNames));
92       case ASTNode.TYPE_DECLARATION:
93       case ASTNode.ENUM_DECLARATION:
94         return ImmutableList.<BodyDeclarationLocator>of(typeLocator);
95       case ASTNode.ENUM_CONSTANT_DECLARATION:
96         EnumConstantDeclaration enumConstantDeclaration = (EnumConstantDeclaration) bodyDeclaration;
97         String constantName = enumConstantDeclaration.getName().getIdentifier();
98         return ImmutableList.<BodyDeclarationLocator>of(
99             new EnumConstantLocator(typeLocator, constantName));
100       default:
101         throw new IllegalArgumentException("Unsupported node type: " + nodeType);
102     }
103   }
104 
105   /**
106    * Creates a {@link BodyDeclarationLocator} from a string form of a body declaration.
107    * See {@link #toLocatorStringForms(BodyDeclaration)}.
108    */
fromStringForm(String stringForm)109   public static BodyDeclarationLocator fromStringForm(String stringForm) {
110     List<String> components = splitInTwo(stringForm, ":");
111     String locatorTypeName = components.get(0);
112     String locatorString = components.get(1);
113     switch (locatorTypeName) {
114       case FieldLocator.LOCATOR_TYPE_NAME:
115         List<String> typeAndField = splitInTwo(locatorString, "#");
116         return new FieldLocator(new TypeLocator(typeAndField.get(0)), typeAndField.get(1));
117       case MethodLocator.LOCATOR_TYPE_NAME:
118         List<String> typeAndMethod = splitInTwo(locatorString, "#");
119         String methodNameAndParameters = typeAndMethod.get(1);
120         int parameterStartIndex = methodNameAndParameters.indexOf('(');
121         if (parameterStartIndex == -1) {
122           throw new IllegalArgumentException("No '(' found in " + methodNameAndParameters);
123         }
124         String methodName = methodNameAndParameters.substring(0, parameterStartIndex);
125         String parametersString = methodNameAndParameters.substring(parameterStartIndex);
126         List<String> parameterTypes = extractParameterTypes(parametersString);
127         return new MethodLocator(new TypeLocator(typeAndMethod.get(0)), methodName, parameterTypes);
128       case TypeLocator.LOCATOR_TYPE_NAME:
129         return new TypeLocator(locatorString);
130       case EnumConstantLocator.LOCATOR_TYPE_NAME:
131         List<String> typeAndConstant = splitInTwo(locatorString, "#");
132         return new EnumConstantLocator(
133             new TypeLocator(typeAndConstant.get(0)), typeAndConstant.get(1));
134       default:
135         throw new IllegalArgumentException("Unsupported locator type: " + locatorTypeName);
136     }
137   }
138 
matchesAny(List<BodyDeclarationLocator> locators, BodyDeclaration node)139   public static boolean matchesAny(List<BodyDeclarationLocator> locators, BodyDeclaration node) {
140     for (BodyDeclarationLocator locator : locators) {
141       if (locator.matches(node)) {
142         return true;
143       }
144     }
145     return false;
146   }
147 
148   /**
149    * Finds the declaration associated with a given node. If the node is not a child of a declaration
150    * {@code null} is returned.
151    */
findDeclarationNode(ASTNode node)152   public static BodyDeclaration findDeclarationNode(ASTNode node) {
153     ASTNode ancestor = node;
154     while (ancestor != null && !(ancestor instanceof BodyDeclaration)) {
155       ancestor = ancestor.getParent();
156     }
157 
158     return ancestor instanceof BodyDeclaration ? (BodyDeclaration) ancestor : null;
159   }
160 
extractParameterTypes(String parametersString)161   private static List<String> extractParameterTypes(String parametersString) {
162     if (!(parametersString.startsWith("(") && parametersString.endsWith(")"))) {
163       throw new IllegalArgumentException("Expected \"(<types>)\" but was " + parametersString);
164     }
165     parametersString = parametersString.substring(1, parametersString.length() - 1);
166     if (parametersString.isEmpty()) {
167       return Collections.emptyList();
168     }
169     return splitParameters(parametersString);
170   }
171 
172   /**
173    * Split parameters by ','. Support simple generics syntax.
174    * TODO: Replace the implementation by JDT parser.
175    */
splitParameters(String parametersString)176   private static List<String> splitParameters(String parametersString) {
177     List<String> result = new ArrayList<>();
178     int genericsLevel = 0;
179     int start = 0;
180     for (int p = 0; p < parametersString.length(); p++) {
181       char c = parametersString.charAt(p);
182       if (genericsLevel == 0 && c == ',') {
183         result.add(parametersString.substring(start, p));
184         start = p + 1;
185       } else if (c == '<') {
186         genericsLevel++;
187       } else if (c == '>') {
188         genericsLevel--;
189       }
190     }
191     if (start < parametersString.length()) {
192       result.add(parametersString.substring(start, parametersString.length()));
193     }
194     return result;
195   }
196 
197 
splitInTwo(String string, String separator)198   private static List<String> splitInTwo(String string, String separator) {
199     List<String> components = Splitter.on(separator).splitToList(string);
200     if (components.size() != 2) {
201       throw new IllegalArgumentException("Cannot split " + string + " on " + separator);
202     }
203     return components;
204   }
205 
206   /**
207    * Read body declarations from a file.
208    *
209    * <p>Blank lines and lines starting with a {@code #} are ignored.
210    *
211    * @param path the path to the file.
212    * @return The list of {@link BodyDeclarationLocator} instances.
213    */
readBodyDeclarationLocators(Path path)214   public static List<BodyDeclarationLocator> readBodyDeclarationLocators(Path path) {
215     return createLocatorsFromStrings(TypeLocator.readLines(path));
216   }
217 }
218