| /* |
| * Copyright 2009 Google Inc. |
| * |
| * 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.google.gwt.dev.javac; |
| |
| import com.google.gwt.core.ext.typeinfo.JType; |
| import com.google.gwt.dev.javac.typemodel.JAbstractMethod; |
| import com.google.gwt.dev.javac.typemodel.JClassType; |
| import com.google.gwt.dev.javac.typemodel.JParameter; |
| import com.google.gwt.dev.jjs.InternalCompilerException; |
| import com.google.gwt.dev.resource.Resource; |
| import com.google.gwt.dev.util.Name.BinaryName; |
| import com.google.gwt.dev.util.Util; |
| |
| import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.TypeReference; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; |
| import org.eclipse.jdt.internal.core.util.CodeSnippetParsingUtil; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.ref.SoftReference; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.WeakHashMap; |
| |
| /** |
| * Methods to do direct parsing of Java source -- currently the only uses are |
| * for finding actual method parameter names on request. |
| */ |
| public class JavaSourceParser { |
| |
| public static JClassType getTopmostType(JClassType type) { |
| while (type.getEnclosingType() != null) { |
| type = type.getEnclosingType(); |
| } |
| return type; |
| } |
| |
| /** |
| * Spits a binary name into a series of char arrays, corresponding to |
| * enclosing classes. |
| * |
| * <p> |
| * For example, {@code test.Foo$Bar} gets expanded to [[Foo],[Bar]]. Note that |
| * the package is not included. |
| * |
| * @param binaryName class name in binary form (ie, test.Foo$Bar) |
| * @return list of char arrays of class names, from outer to inner |
| */ |
| // @VisibleForTesting |
| static List<char[]> getClassChain(String binaryName) { |
| ArrayList<char[]> result = new ArrayList<char[]>(); |
| String className = BinaryName.getClassName(binaryName); |
| int idx; |
| while ((idx = className.indexOf('$')) >= 0) { |
| result.add(className.substring(0, idx).toCharArray()); |
| className = className.substring(idx + 1); |
| } |
| result.add(className.toCharArray()); |
| return result; |
| } |
| |
| /** |
| * Find a matching method in a type. |
| * |
| * @param type JDT method |
| * @param jMethod TypeOracle method object to find |
| * @return method declaration or null if not found |
| */ |
| private static AbstractMethodDeclaration findMethod(TypeDeclaration type, |
| JAbstractMethod jMethod) { |
| List<AbstractMethodDeclaration> candidates = findNamedMethods(type, |
| jMethod.getName()); |
| if (candidates.size() == 0) { |
| return null; |
| } |
| if (candidates.size() == 1) { |
| return candidates.get(0); |
| } |
| nextCandidate : for (AbstractMethodDeclaration candidate : candidates) { |
| int n = candidate.arguments == null ? 0 : candidate.arguments.length; |
| JParameter[] params = jMethod.getParameters(); |
| if (n != params.length) { |
| continue; |
| } |
| for (int i = 0; i < n; ++i) { |
| if (!typeMatches(candidate.arguments[i].type, params[i].getType())) { |
| continue nextCandidate; |
| } |
| } |
| return candidate; |
| } |
| return null; |
| } |
| |
| /** |
| * Find all methods which have the requested name. |
| * |
| * <p> |
| * {@code <clinit>} is not supported. |
| * |
| * @param type JDT type declaration |
| * @param name name of methods to find |
| * @return list of matching methods |
| */ |
| private static List<AbstractMethodDeclaration> findNamedMethods( |
| TypeDeclaration type, String name) { |
| List<AbstractMethodDeclaration> matching = new ArrayList<AbstractMethodDeclaration>(); |
| boolean isCtor = "<init>".equals(name); |
| char[] nameArray = name.toCharArray(); |
| for (AbstractMethodDeclaration method : type.methods) { |
| if ((isCtor && method.isConstructor()) |
| || (!isCtor && !method.isConstructor() && !method.isClinit() && Arrays.equals( |
| method.selector, nameArray))) { |
| matching.add(method); |
| } |
| } |
| return matching; |
| } |
| |
| /** |
| * Find a particular type in a compilation unit. |
| * |
| * @param unit JDT cud |
| * @param binaryName binary name of the type to find (ie, test.Foo$Bar) |
| * @return type declaration or null if not found |
| */ |
| private static TypeDeclaration findType(CompilationUnitDeclaration unit, |
| String binaryName) { |
| List<char[]> classChain = getClassChain(binaryName); |
| TypeDeclaration curType = findType(unit.types, classChain.get(0)); |
| for (int i = 1; i < classChain.size(); ++i) { |
| if (curType == null) { |
| return null; |
| } |
| curType = findType(curType.memberTypes, classChain.get(i)); |
| } |
| return curType; |
| } |
| |
| /** |
| * Find one type by name in a array of types. |
| * |
| * @param types array of types |
| * @param name name of type to find |
| * @return matching type or null if not found |
| */ |
| private static TypeDeclaration findType(TypeDeclaration[] types, char[] name) { |
| for (TypeDeclaration type : types) { |
| if (Arrays.equals(name, type.name)) { |
| return type; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Parse Java source. |
| * |
| * @param javaSource String containing Java source to parse |
| * @return a CompilationUnitDeclaration or null if parsing failed |
| */ |
| private static CompilationUnitDeclaration parseJava(String javaSource) { |
| CodeSnippetParsingUtil parsingUtil = new CodeSnippetParsingUtil(true); |
| CompilerOptions options = new CompilerOptions(); |
| options.complianceLevel = ClassFileConstants.JDK1_8; |
| options.originalSourceLevel = ClassFileConstants.JDK1_8; |
| options.sourceLevel = ClassFileConstants.JDK1_8; |
| CompilationUnitDeclaration unit = parsingUtil.parseCompilationUnit( |
| javaSource.toString().toCharArray(), options.getMap(), true); |
| if (unit.compilationResult().hasProblems()) { |
| return null; |
| } |
| return unit; |
| } |
| |
| /** |
| * Compares an unresolved JDT type to a TypeOracle type to see if they match. |
| * |
| * @param jdtType |
| * @param toType |
| * @return true if the two type objects resolve to the same |
| */ |
| private static boolean typeMatches(TypeReference jdtType, JType toType) { |
| List<char[]> toNameComponents = getClassChain(toType.getQualifiedBinaryName()); |
| int toLen = toNameComponents.size(); |
| char[][] jdtNameComponents = jdtType.getTypeName(); |
| int jdtLen = jdtNameComponents.length; |
| int maxToCompare = Math.min(toLen, jdtLen); |
| |
| // compare from the end |
| for (int i = 1; i <= maxToCompare; ++i) { |
| if (!Arrays.equals(jdtNameComponents[jdtLen - i], |
| toNameComponents.get(toLen - i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Map of top-level classes to the source file associated with it. |
| */ |
| private WeakHashMap<JClassType, Resource> classSources = new WeakHashMap<JClassType, Resource>(); |
| |
| /** |
| * Cache of top-level classes to JDT CUDs associated with them. |
| * |
| * <p> |
| * CUDs may be discarded at any time (with a performance cost if they are |
| * needed again), and are held in SoftReferences to allow GC to dump them. |
| */ |
| private WeakHashMap<JClassType, SoftReference<CompilationUnitDeclaration>> cudCache = new WeakHashMap<JClassType, SoftReference<CompilationUnitDeclaration>>(); |
| |
| /** |
| * Add a source file associated with the outermost enclosing class. |
| * |
| * @param topType |
| * @param source |
| * |
| * TODO: reduce visibility |
| */ |
| public synchronized void addSourceForType(JClassType topType, Resource source) { |
| classSources.put(topType, source); |
| } |
| |
| /** |
| * Return the real argument names for a given method from the source. |
| * |
| * @param method method to lookup parameter names for |
| * @return array of argument names or null if no source is available |
| */ |
| public synchronized String[] getArguments(JAbstractMethod method) { |
| JClassType type = method.getEnclosingType(); |
| JClassType topType = getTopmostType(type); |
| CompilationUnitDeclaration cud = getCudForTopLevelType(topType); |
| if (cud == null) { |
| return null; |
| } |
| TypeDeclaration jdtType = findType(cud, type.getQualifiedBinaryName()); |
| if (jdtType == null) { |
| // TODO(jat): any thing else to do here? |
| return null; |
| } |
| AbstractMethodDeclaration jdtMethod = findMethod(jdtType, method); |
| if (jdtMethod == null) { |
| // TODO(jat): any thing else to do here? |
| return null; |
| } |
| int n = jdtMethod.arguments.length; |
| String[] argNames = new String[n]; |
| for (int i = 0; i < n; ++i) { |
| argNames[i] = String.valueOf(jdtMethod.arguments[i].name); |
| } |
| return argNames; |
| } |
| |
| /** |
| * Finds a JDT CUD for a given top-level type, generating it if needed. |
| * |
| * @param topType top-level JClassType |
| * @return CUD instance or null if no source found |
| */ |
| private synchronized CompilationUnitDeclaration getCudForTopLevelType( |
| JClassType topType) { |
| CompilationUnitDeclaration cud = null; |
| if (cudCache.containsKey(topType)) { |
| SoftReference<CompilationUnitDeclaration> cudRef = cudCache.get(topType); |
| if (cudRef != null) { |
| cud = cudRef.get(); |
| } |
| } |
| if (cud == null) { |
| Resource classSource = classSources.get(topType); |
| String source = null; |
| if (classSource != null) { |
| try { |
| InputStream stream = classSource.openContents(); |
| source = Util.readStreamAsString(stream); |
| } catch (IOException ex) { |
| throw new InternalCompilerException("Problem reading resource: " |
| + classSource.getLocation(), ex); |
| } |
| } |
| if (source == null) { |
| // cache negative result so we don't try again |
| cudCache.put(topType, null); |
| } else { |
| cud = parseJava(source); |
| cudCache.put(topType, |
| new SoftReference<CompilationUnitDeclaration>(cud)); |
| } |
| } |
| return cud; |
| } |
| } |