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 org.eclipse.jdt.core.dom.ASTNode;
22 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
23 import org.eclipse.jdt.core.dom.BodyDeclaration;
24 import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
25 import org.eclipse.jdt.core.dom.FieldDeclaration;
26 import org.eclipse.jdt.core.dom.MethodDeclaration;
27 
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.List;
31 
32 /**
33  * Utility methods associated with {@link BodyDeclarationLocator} and its standard implementations.
34  */
35 public final class BodyDeclarationLocators {
36 
BodyDeclarationLocators()37   private BodyDeclarationLocators() {
38   }
39 
createLocatorsFromStrings(String[] locatorStrings)40   public static List<BodyDeclarationLocator> createLocatorsFromStrings(String[] locatorStrings) {
41     ImmutableList.Builder<BodyDeclarationLocator> locatorListBuilder = ImmutableList.builder();
42     for (String locatorString : locatorStrings) {
43       BodyDeclarationLocator locator = BodyDeclarationLocators.fromStringForm(locatorString);
44       locatorListBuilder.add(locator);
45     }
46     return locatorListBuilder.build();
47   }
48 
49   /**
50    * Generates strings that can be used with {@link #fromStringForm(String)} to generate
51    * {@link BodyDeclarationLocator} instances capable of locating the supplied node. Usually returns
52    * a single element, except for fields declarations.
53    */
toLocatorStringForms(BodyDeclaration bodyDeclaration)54   public static List<String> toLocatorStringForms(BodyDeclaration bodyDeclaration) {
55     List<BodyDeclarationLocator> locators = createLocators(bodyDeclaration);
56     List<String> stringForms = new ArrayList<>(locators.size());
57     for (BodyDeclarationLocator locator : locators) {
58       stringForms.add(locator.getStringFormType() + ":" + locator.getStringFormTarget());
59     }
60     return stringForms;
61   }
62 
63   /**
64    * Creates {@link BodyDeclarationLocator} objects that can find the supplied
65    * {@link BodyDeclaration}. Usually returns a single element, except for fields declarations.
66    */
createLocators(BodyDeclaration bodyDeclaration)67   public static List<BodyDeclarationLocator> createLocators(BodyDeclaration bodyDeclaration) {
68     AbstractTypeDeclaration typeDeclaration = TypeLocator.findTypeDeclarationNode(bodyDeclaration);
69     if (typeDeclaration == null) {
70       throw new AssertionError("Unable to find type declaration for " + typeDeclaration);
71     }
72     TypeLocator typeLocator = new TypeLocator(typeDeclaration);
73 
74     int nodeType = bodyDeclaration.getNodeType();
75     switch (nodeType) {
76       case ASTNode.FIELD_DECLARATION:
77         List<String> fieldNames = FieldLocator.getFieldNames((FieldDeclaration) bodyDeclaration);
78         List<BodyDeclarationLocator> fieldLocators = new ArrayList<>(fieldNames.size());
79         for (String fieldName : fieldNames) {
80           fieldLocators.add(new FieldLocator(typeLocator, fieldName));
81         }
82         return fieldLocators;
83       case ASTNode.METHOD_DECLARATION:
84         MethodDeclaration methodDeclaration = (MethodDeclaration) bodyDeclaration;
85         List<String> parameterTypeNames = ParameterMatcher.getParameterTypeNames(methodDeclaration);
86         return ImmutableList.<BodyDeclarationLocator>of(
87             new MethodLocator(typeLocator, methodDeclaration.getName().getIdentifier(),
88             parameterTypeNames));
89       case ASTNode.TYPE_DECLARATION:
90       case ASTNode.ENUM_DECLARATION:
91         return ImmutableList.<BodyDeclarationLocator>of(typeLocator);
92       case ASTNode.ENUM_CONSTANT_DECLARATION:
93         EnumConstantDeclaration enumConstantDeclaration = (EnumConstantDeclaration) bodyDeclaration;
94         String constantName = enumConstantDeclaration.getName().getIdentifier();
95         return ImmutableList.<BodyDeclarationLocator>of(
96             new EnumConstantLocator(typeLocator, constantName));
97       default:
98         throw new IllegalArgumentException("Unsupported node type: " + nodeType);
99     }
100   }
101 
102   /**
103    * Creates a {@link BodyDeclarationLocator} from a string form of a body declaration.
104    * See {@link #toLocatorStringForms(BodyDeclaration)}.
105    */
fromStringForm(String stringForm)106   public static BodyDeclarationLocator fromStringForm(String stringForm) {
107     List<String> components = splitInTwo(stringForm, ":");
108     String locatorTypeName = components.get(0);
109     String locatorString = components.get(1);
110     switch (locatorTypeName) {
111       case FieldLocator.LOCATOR_TYPE_NAME:
112         List<String> typeAndField = splitInTwo(locatorString, "#");
113         return new FieldLocator(new TypeLocator(typeAndField.get(0)), typeAndField.get(1));
114       case MethodLocator.LOCATOR_TYPE_NAME:
115         List<String> typeAndMethod = splitInTwo(locatorString, "#");
116         String methodNameAndParameters = typeAndMethod.get(1);
117         int parameterStartIndex = methodNameAndParameters.indexOf('(');
118         if (parameterStartIndex == -1) {
119           throw new IllegalArgumentException("No '(' found in " + methodNameAndParameters);
120         }
121         String methodName = methodNameAndParameters.substring(0, parameterStartIndex);
122         String parametersString = methodNameAndParameters.substring(parameterStartIndex);
123         List<String> parameterTypes = extractParameterTypes(parametersString);
124         return new MethodLocator(new TypeLocator(typeAndMethod.get(0)), methodName, parameterTypes);
125       case TypeLocator.LOCATOR_TYPE_NAME:
126         return new TypeLocator(locatorString);
127       case EnumConstantLocator.LOCATOR_TYPE_NAME:
128         List<String> typeAndConstant = splitInTwo(locatorString, "#");
129         return new EnumConstantLocator(
130             new TypeLocator(typeAndConstant.get(0)), typeAndConstant.get(1));
131       default:
132         throw new IllegalArgumentException("Unsupported locator type: " + locatorTypeName);
133     }
134   }
135 
matchesAny(List<BodyDeclarationLocator> locators, BodyDeclaration node)136   public static boolean matchesAny(List<BodyDeclarationLocator> locators, BodyDeclaration node) {
137     for (BodyDeclarationLocator locator : locators) {
138       if (locator.matches(node)) {
139         return true;
140       }
141     }
142     return false;
143   }
144 
145   /**
146    * Finds the declaration associated with a given node. If the node is not a child of a declaration
147    * {@code null} is returned.
148    */
findDeclarationNode(ASTNode node)149   public static BodyDeclaration findDeclarationNode(ASTNode node) {
150     ASTNode ancestor = node;
151     while (ancestor != null && !(ancestor instanceof BodyDeclaration)) {
152       ancestor = ancestor.getParent();
153     }
154 
155     return ancestor instanceof BodyDeclaration ? (BodyDeclaration) ancestor : null;
156   }
157 
extractParameterTypes(String parametersString)158   private static List<String> extractParameterTypes(String parametersString) {
159     if (!(parametersString.startsWith("(") && parametersString.endsWith(")"))) {
160       throw new IllegalArgumentException("Expected \"(<types>)\" but was " + parametersString);
161     }
162     parametersString = parametersString.substring(1, parametersString.length() - 1);
163     if (parametersString.isEmpty()) {
164       return Collections.emptyList();
165     }
166     return Splitter.on(',').splitToList(parametersString);
167   }
168 
splitInTwo(String string, String separator)169   private static List<String> splitInTwo(String string, String separator) {
170     List<String> components = Splitter.on(separator).splitToList(string);
171     if (components.size() != 2) {
172       throw new IllegalArgumentException("Cannot split " + string + " on " + separator);
173     }
174     return components;
175   }
176 }
177