1 /*
2  * Copyright 2016 Federico Tomassetti
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 
17 package com.github.javaparser.symbolsolver.javaparser;
18 
19 import com.github.javaparser.ast.CompilationUnit;
20 import com.github.javaparser.ast.Node;
21 import com.github.javaparser.ast.body.*;
22 import com.github.javaparser.ast.expr.MethodCallExpr;
23 import com.github.javaparser.ast.expr.NameExpr;
24 import com.github.javaparser.ast.expr.SimpleName;
25 import com.github.javaparser.ast.stmt.ReturnStmt;
26 import com.github.javaparser.ast.stmt.SwitchStmt;
27 
28 import java.util.List;
29 import java.util.Optional;
30 
31 /**
32  * This class can be used to easily retrieve nodes from a JavaParser AST.
33  *
34  * @author Federico Tomassetti
35  */
36 public final class Navigator {
37 
Navigator()38     private Navigator() {
39         // prevent instantiation
40     }
41 
requireParentNode(Node node)42     public static Node requireParentNode(Node node) {
43         return node.getParentNode().orElseThrow(() -> new IllegalStateException("Parent not found, the node does not appear to be inserted in a correct AST"));
44     }
45 
46     /**
47      * Looks among the type declared in the Compilation Unit for one having the specified name.
48      * The name can be qualified with respect to the compilation unit. For example, if the compilation
49      * unit is in package a.b; and it contains two top level classes named C and D, with class E being defined inside D
50      * then the qualifiedName that can be resolved are "C", "D", and "D.E".
51      */
findType(CompilationUnit cu, String qualifiedName)52     public static Optional<TypeDeclaration<?>> findType(CompilationUnit cu, String qualifiedName) {
53         if (cu.getTypes().isEmpty()) {
54             return Optional.empty();
55         }
56 
57         final String typeName = getOuterTypeName(qualifiedName);
58         Optional<TypeDeclaration<?>> type = cu.getTypes().stream().filter((t) -> t.getName().getId().equals(typeName)).findFirst();
59 
60         final String innerTypeName = getInnerTypeName(qualifiedName);
61         if (type.isPresent() && !innerTypeName.isEmpty()) {
62             return findType(type.get(), innerTypeName);
63         }
64         return type;
65     }
66 
67     /**
68      * Looks among the type declared in the TypeDeclaration for one having the specified name.
69      * The name can be qualified with respect to the TypeDeclaration. For example, if the class declarationd defines class D
70      * and class D contains an internal class named E then the qualifiedName that can be resolved are "D", and "D.E".
71      */
findType(TypeDeclaration<?> td, String qualifiedName)72     public static Optional<TypeDeclaration<?>> findType(TypeDeclaration<?> td, String qualifiedName) {
73         final String typeName = getOuterTypeName(qualifiedName);
74 
75         Optional<TypeDeclaration<?>> type = Optional.empty();
76         for (Node n : td.getMembers()) {
77             if (n instanceof TypeDeclaration && ((TypeDeclaration<?>) n).getName().getId().equals(typeName)) {
78                 type = Optional.of((TypeDeclaration<?>) n);
79                 break;
80             }
81         }
82         final String innerTypeName = getInnerTypeName(qualifiedName);
83         if (type.isPresent() && !innerTypeName.isEmpty()) {
84             return findType(type.get(), innerTypeName);
85         }
86         return type;
87     }
88 
demandClass(CompilationUnit cu, String qualifiedName)89     public static ClassOrInterfaceDeclaration demandClass(CompilationUnit cu, String qualifiedName) {
90         ClassOrInterfaceDeclaration cd = demandClassOrInterface(cu, qualifiedName);
91         if (cd.isInterface()) {
92             throw new IllegalStateException("Type is not a class");
93         }
94         return cd;
95     }
96 
demandInterface(CompilationUnit cu, String qualifiedName)97     public static ClassOrInterfaceDeclaration demandInterface(CompilationUnit cu, String qualifiedName) {
98         ClassOrInterfaceDeclaration cd = demandClassOrInterface(cu, qualifiedName);
99         if (!cd.isInterface()) {
100             throw new IllegalStateException("Type is not an interface");
101         }
102         return cd;
103     }
104 
demandEnum(CompilationUnit cu, String qualifiedName)105     public static EnumDeclaration demandEnum(CompilationUnit cu, String qualifiedName) {
106         Optional<TypeDeclaration<?>> res = findType(cu, qualifiedName);
107         if (!res.isPresent()) {
108             throw new IllegalStateException("No type found");
109         }
110         if (!(res.get() instanceof EnumDeclaration)) {
111             throw new IllegalStateException("Type is not an enum");
112         }
113         return (EnumDeclaration) res.get();
114     }
115 
demandMethod(TypeDeclaration<?> cd, String name)116     public static MethodDeclaration demandMethod(TypeDeclaration<?> cd, String name) {
117         MethodDeclaration found = null;
118         for (BodyDeclaration<?> bd : cd.getMembers()) {
119             if (bd instanceof MethodDeclaration) {
120                 MethodDeclaration md = (MethodDeclaration) bd;
121                 if (md.getNameAsString().equals(name)) {
122                     if (found != null) {
123                         throw new IllegalStateException("Ambiguous getName");
124                     }
125                     found = md;
126                 }
127             }
128         }
129         if (found == null) {
130             throw new IllegalStateException("No method called " + name);
131         }
132         return found;
133     }
134 
135     /**
136      * Returns the {@code (i+1)}'th constructor of the given type declaration, in textual order. The constructor that
137      * appears first has the index 0, the second one the index 1, and so on.
138      *
139      * @param td    The type declaration to search in. Note that only classes and enums have constructors.
140      * @param index The index of the desired constructor.
141      * @return The desired ConstructorDeclaration if it was found, and {@code null} otherwise.
142      */
demandConstructor(TypeDeclaration<?> td, int index)143     public static ConstructorDeclaration demandConstructor(TypeDeclaration<?> td, int index) {
144         ConstructorDeclaration found = null;
145         int i = 0;
146         for (BodyDeclaration<?> bd : td.getMembers()) {
147             if (bd instanceof ConstructorDeclaration) {
148                 ConstructorDeclaration cd = (ConstructorDeclaration) bd;
149                 if (i == index) {
150                     found = cd;
151                     break;
152                 }
153                 i++;
154             }
155         }
156         if (found == null) {
157             throw new IllegalStateException("No constructor with index " + index);
158         }
159         return found;
160     }
161 
demandField(ClassOrInterfaceDeclaration cd, String name)162     public static VariableDeclarator demandField(ClassOrInterfaceDeclaration cd, String name) {
163         for (BodyDeclaration<?> bd : cd.getMembers()) {
164             if (bd instanceof FieldDeclaration) {
165                 FieldDeclaration fd = (FieldDeclaration) bd;
166                 for (VariableDeclarator vd : fd.getVariables()) {
167                     if (vd.getName().getId().equals(name)) {
168                         return vd;
169                     }
170                 }
171             }
172         }
173         throw new IllegalStateException("No field with given name");
174     }
175 
findNameExpression(Node node, String name)176     public static Optional<NameExpr> findNameExpression(Node node, String name) {
177         return node.findFirst(NameExpr.class, n -> n.getNameAsString().equals(name));
178     }
179 
findSimpleName(Node node, String name)180     public static Optional<SimpleName> findSimpleName(Node node, String name) {
181         return node.findFirst(SimpleName.class, n -> n.asString().equals(name));
182     }
183 
184 
findMethodCall(Node node, String methodName)185     public static Optional<MethodCallExpr> findMethodCall(Node node, String methodName) {
186         return node.findFirst(MethodCallExpr.class, n -> n.getNameAsString().equals(methodName));
187     }
188 
demandVariableDeclaration(Node node, String name)189     public static Optional<VariableDeclarator> demandVariableDeclaration(Node node, String name) {
190         return node.findFirst(VariableDeclarator.class, n -> n.getNameAsString().equals(name));
191     }
192 
demandClassOrInterface(CompilationUnit compilationUnit, String qualifiedName)193     public static ClassOrInterfaceDeclaration demandClassOrInterface(CompilationUnit compilationUnit, String qualifiedName) {
194         return findType(compilationUnit, qualifiedName)
195                 .map(res -> res.toClassOrInterfaceDeclaration().orElseThrow(() -> new IllegalStateException("Type is not a class or an interface, it is " + res.getClass().getCanonicalName())))
196                 .orElseThrow(() -> new IllegalStateException("No type named '" + qualifiedName + "'found"));
197     }
198 
199     // TODO should be demand or requireSwitch
findSwitch(Node node)200     public static SwitchStmt findSwitch(Node node) {
201         return findSwitchHelper(node).orElseThrow(IllegalArgumentException::new);
202     }
203 
findNodeOfGivenClass(Node node, Class<N> clazz)204     public static <N extends Node> N findNodeOfGivenClass(Node node, Class<N> clazz) {
205         return node.findFirst(clazz).orElseThrow(IllegalArgumentException::new);
206     }
207 
208     // TODO should be demand or require...
findReturnStmt(MethodDeclaration method)209     public static ReturnStmt findReturnStmt(MethodDeclaration method) {
210         return findNodeOfGivenClass(method, ReturnStmt.class);
211     }
212 
213     ///
214     /// Private methods
215     ///
216 
getOuterTypeName(String qualifiedName)217     private static String getOuterTypeName(String qualifiedName) {
218         return qualifiedName.split("\\.", 2)[0];
219     }
220 
getInnerTypeName(String qualifiedName)221     private static String getInnerTypeName(String qualifiedName) {
222         if (qualifiedName.contains(".")) {
223             return qualifiedName.split("\\.", 2)[1];
224         }
225         return "";
226     }
227 
findSwitchHelper(Node node)228     private static Optional<SwitchStmt> findSwitchHelper(Node node) {
229         // TODO can be replaced by findFirst with the correct algorithm.
230         if (node instanceof SwitchStmt) {
231             return Optional.of((SwitchStmt) node);
232         }
233         for (Node child : node.getChildNodes()) {
234             Optional<SwitchStmt> resChild = findSwitchHelper(child);
235             if (resChild.isPresent()) {
236                 return resChild;
237             }
238         }
239         return Optional.empty();
240     }
241 }
242