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