Revision: 5848 Author: [email protected] Date: Thu Jul 30 15:12:48 2009 Log: Finish support for parsing Java source to get argument names, still needs to be hooked in.
http://code.google.com/p/google-web-toolkit/source/detail?r=5848 Added: /changes/jat/ihm/dev/core/test/com/google/gwt/dev/javac/CompilationStateTestBase.java Modified: /changes/jat/ihm/dev/core/src/com/google/gwt/dev/javac/JavaSourceParser.java /changes/jat/ihm/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java /changes/jat/ihm/dev/core/test/com/google/gwt/dev/javac/JavaSourceParserTest.java ======================================= --- /dev/null +++ /changes/jat/ihm/dev/core/test/com/google/gwt/dev/javac/CompilationStateTestBase.java Thu Jul 30 15:12:48 2009 @@ -0,0 +1,173 @@ +/* + * Copyright 2008 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.TreeLogger; +import com.google.gwt.dev.javac.CompilationUnit.State; +import com.google.gwt.dev.javac.impl.JavaResourceBase; +import com.google.gwt.dev.javac.impl.MockJavaResource; +import com.google.gwt.dev.javac.impl.MockResourceOracle; +import com.google.gwt.dev.javac.impl.SourceFileCompilationUnit; +import com.google.gwt.dev.resource.Resource; +import com.google.gwt.dev.shell.StandardGeneratorContext.Generated; +import com.google.gwt.dev.util.Util; +import com.google.gwt.dev.util.log.AbstractTreeLogger; +import com.google.gwt.dev.util.log.PrintWriterTreeLogger; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +/** + * Base class for tests that need a mock type compilation state and eveything + * that goes with it (compilation units, type oracle, resources, ...). + */ +public class CompilationStateTestBase extends TestCase { + + protected static class GeneratedSourceFileCompilationUnit extends + SourceFileCompilationUnit implements Generated { + + private final boolean modifySource; + private String strongHash; + + public GeneratedSourceFileCompilationUnit(Resource sourceFile, boolean modifySource) { + super(sourceFile); + this.modifySource = modifySource; + } + + public void abort() { + } + + public void commit() { + } + + @Override + public String getSource() { + String extraChars = ""; + if (modifySource) { + extraChars = "\n"; + } + return super.getSource() + extraChars; + } + + public String getStrongHash() { + if (strongHash == null) { + strongHash = Util.computeStrongName(Util.getBytes(getSource())); + } + return strongHash; + } + + @Override + public boolean isGenerated() { + return true; + } + } + + static void assertUnitsChecked(Collection<CompilationUnit> units) { + for (CompilationUnit unit : units) { + assertSame(State.CHECKED, unit.getState()); + assertNull(unit.getErrors()); + assertTrue(unit.getCompiledClasses().size() > 0); + } + } + + /** + * Tweak this if you want to see the log output. + */ + protected static TreeLogger createTreeLogger() { + boolean reallyLog = false; + if (reallyLog) { + AbstractTreeLogger logger = new PrintWriterTreeLogger(); + logger.setMaxDetail(TreeLogger.ALL); + return logger; + } + return TreeLogger.NULL; + } + + protected MockResourceOracle oracle = new MockResourceOracle( + JavaResourceBase.getStandardResources()); + + protected CompilationState state = new CompilationState(createTreeLogger(), + oracle); + + protected void addGeneratedUnits(MockJavaResource... sourceFiles) { + Set<CompilationUnit> units = getCompilationUnits(sourceFiles); + state.addGeneratedCompilationUnits(createTreeLogger(), units); + } + + protected Set<CompilationUnit> getCompilationUnits( + MockJavaResource... sourceFiles) { + Set<CompilationUnit> units = new HashSet<CompilationUnit>(); + for (MockJavaResource sourceFile : sourceFiles) { + // keep the same source + units.add(new GeneratedSourceFileCompilationUnit(sourceFile, false)); + } + return units; + } + + protected Set<CompilationUnit> getModifiedCompilationUnits( + MockJavaResource... sourceFiles) { + Set<CompilationUnit> units = new HashSet<CompilationUnit>(); + for (MockJavaResource sourceFile : sourceFiles) { + // modify the source + units.add(new GeneratedSourceFileCompilationUnit(sourceFile, true)); + } + return units; + } + + protected void validateCompilationState(String... generatedTypeNames) { + // Save off the reflected collections. + Map<String, CompilationUnit> unitMap = state.getCompilationUnitMap(); + Collection<CompilationUnit> units = state.getCompilationUnits(); + + // Validate that the collections are consistent with each other. + assertEquals(new HashSet<CompilationUnit>(unitMap.values()), + new HashSet<CompilationUnit>(units)); + + // Save off a mutable copy of the source map and generated types to compare. + Map<String, Resource> sourceMap = new HashMap<String, Resource>( + oracle.getResourceMap()); + Set<String> generatedTypes = new HashSet<String>( + Arrays.asList(generatedTypeNames)); + assertEquals(sourceMap.size() + generatedTypes.size(), units.size()); + for (Entry<String, CompilationUnit> entry : unitMap.entrySet()) { + // Validate source file internally consistent. + String className = entry.getKey(); + CompilationUnit unit = entry.getValue(); + assertEquals(className, unit.getTypeName()); + + // Find the matching resource (and remove it). + if (unit.isGenerated()) { + assertTrue(generatedTypes.contains(className)); + assertNotNull(generatedTypes.remove(className)); + } else { + String partialPath = className.replace('.', '/') + ".java"; + assertTrue(sourceMap.containsKey(partialPath)); + // TODO: Validate the source file matches the resource. + assertNotNull(sourceMap.remove(partialPath)); + } + } + // The mutable sets should be empty now. + assertEquals(0, sourceMap.size()); + assertEquals(0, generatedTypes.size()); + } +} ======================================= --- /changes/jat/ihm/dev/core/src/com/google/gwt/dev/javac/JavaSourceParser.java Thu Jul 30 09:28:03 2009 +++ /changes/jat/ihm/dev/core/src/com/google/gwt/dev/javac/JavaSourceParser.java Thu Jul 30 15:12:48 2009 @@ -16,18 +16,27 @@ package com.google.gwt.dev.javac; import com.google.gwt.core.ext.typeinfo.JAbstractMethod; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JParameter; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.dev.resource.Resource; +import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.Name.BinaryName; 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.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 @@ -35,23 +44,11 @@ */ public class JavaSourceParser { - /** - * 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(); - CompilerOptions options = new CompilerOptions(); - options.complianceLevel = ClassFileConstants.JDK1_5; - options.sourceLevel = ClassFileConstants.JDK1_5; - CompilationUnitDeclaration unit = parsingUtil.parseCompilationUnit( - javaSource.toString().toCharArray(), options.getMap(), true); - if (unit.compilationResult().hasProblems()) { - return null; - } - return unit; + public static JClassType getTopmostType(JClassType type) { + while (type.getEnclosingType() != null) { + type = type.getEnclosingType(); + } + return type; } /** @@ -91,9 +88,24 @@ if (candidates.size() == 0) { return null; } - // TODO(jat): check overloads - assert candidates.size() == 1; - return candidates.get(0); + if (candidates.size() == 1) { + return candidates.get(0); + } + nextCandidate: for (AbstractMethodDeclaration candidate : candidates) { + int n = 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; } /** @@ -154,4 +166,119 @@ } 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(); + CompilerOptions options = new CompilerOptions(); + options.complianceLevel = ClassFileConstants.JDK1_5; + options.sourceLevel = ClassFileConstants.JDK1_5; + 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>>(); + + /** + * 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()); + AbstractMethodDeclaration jdtMethod = findMethod(jdtType, method); + 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; + } + + synchronized void addSourceForType(JClassType topType, Resource source) { + classSources.put(topType, source); + } + + /** + * 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); + InputStream stream = classSource.openContents(); + String source = Util.readStreamAsString(stream); + 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; + } +} ======================================= --- /changes/jat/ihm/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java Wed Jul 8 15:56:20 2009 +++ /changes/jat/ihm/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java Thu Jul 30 15:12:48 2009 @@ -15,99 +15,21 @@ */ package com.google.gwt.dev.javac; -import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.dev.javac.CompilationUnit.State; import com.google.gwt.dev.javac.impl.JavaResourceBase; import com.google.gwt.dev.javac.impl.MockJavaResource; import com.google.gwt.dev.javac.impl.MockResourceOracle; import com.google.gwt.dev.javac.impl.SourceFileCompilationUnit; -import com.google.gwt.dev.resource.Resource; -import com.google.gwt.dev.shell.StandardGeneratorContext.Generated; -import com.google.gwt.dev.util.Util; -import com.google.gwt.dev.util.log.AbstractTreeLogger; -import com.google.gwt.dev.util.log.PrintWriterTreeLogger; - -import junit.framework.TestCase; - -import java.util.Arrays; -import java.util.Collection; + import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.Map.Entry; /** * Tests {...@link CompilationState}. */ -public class CompilationStateTest extends TestCase { - - private static class GeneratedSourceFileCompilationUnit extends - SourceFileCompilationUnit implements Generated { - - private final boolean modifySource; - private String strongHash; - - public GeneratedSourceFileCompilationUnit(Resource sourceFile, boolean modifySource) { - super(sourceFile); - this.modifySource = modifySource; - } - - public void abort() { - } - - public void commit() { - } - - @Override - public String getSource() { - String extraChars = ""; - if (modifySource) { - extraChars = "\n"; - } - return super.getSource() + extraChars; - } - - public String getStrongHash() { - if (strongHash == null) { - strongHash = Util.computeStrongName(Util.getBytes(getSource())); - } - return strongHash; - } - - @Override - public boolean isGenerated() { - return true; - } - } - - static void assertUnitsChecked(Collection<CompilationUnit> units) { - for (CompilationUnit unit : units) { - assertSame(State.CHECKED, unit.getState()); - assertNull(unit.getErrors()); - assertTrue(unit.getCompiledClasses().size() > 0); - } - } - - /** - * Tweak this if you want to see the log output. - */ - private static TreeLogger createTreeLogger() { - boolean reallyLog = false; - if (reallyLog) { - AbstractTreeLogger logger = new PrintWriterTreeLogger(); - logger.setMaxDetail(TreeLogger.ALL); - return logger; - } - return TreeLogger.NULL; - } - - private MockResourceOracle oracle = new MockResourceOracle( - JavaResourceBase.getStandardResources()); - - private CompilationState state = new CompilationState(createTreeLogger(), - oracle); +public class CompilationStateTest extends CompilationStateTestBase { public void testTypeReferences() { addGeneratedUnits(JavaResourceBase.FOO); @@ -331,31 +253,6 @@ JavaResourceBase.FOO), modifiedUnits, Collections.<String> emptySet(), 2); } - - private void addGeneratedUnits(MockJavaResource... sourceFiles) { - Set<CompilationUnit> units = getCompilationUnits(sourceFiles); - state.addGeneratedCompilationUnits(createTreeLogger(), units); - } - - private Set<CompilationUnit> getCompilationUnits( - MockJavaResource... sourceFiles) { - Set<CompilationUnit> units = new HashSet<CompilationUnit>(); - for (MockJavaResource sourceFile : sourceFiles) { - // keep the same source - units.add(new GeneratedSourceFileCompilationUnit(sourceFile, false)); - } - return units; - } - - private Set<CompilationUnit> getModifiedCompilationUnits( - MockJavaResource... sourceFiles) { - Set<CompilationUnit> units = new HashSet<CompilationUnit>(); - for (MockJavaResource sourceFile : sourceFiles) { - // modify the source - units.add(new GeneratedSourceFileCompilationUnit(sourceFile, true)); - } - return units; - } private void testCaching(MockJavaResource... files) { Set<String> reusedTypes = new HashSet<String>(); @@ -441,41 +338,4 @@ usefulUnits); assertUnitsChecked(state.getCompilationUnits()); } - - private void validateCompilationState(String... generatedTypeNames) { - // Save off the reflected collections. - Map<String, CompilationUnit> unitMap = state.getCompilationUnitMap(); - Collection<CompilationUnit> units = state.getCompilationUnits(); - - // Validate that the collections are consistent with each other. - assertEquals(new HashSet<CompilationUnit>(unitMap.values()), - new HashSet<CompilationUnit>(units)); - - // Save off a mutable copy of the source map and generated types to compare. - Map<String, Resource> sourceMap = new HashMap<String, Resource>( - oracle.getResourceMap()); - Set<String> generatedTypes = new HashSet<String>( - Arrays.asList(generatedTypeNames)); - assertEquals(sourceMap.size() + generatedTypes.size(), units.size()); - for (Entry<String, CompilationUnit> entry : unitMap.entrySet()) { - // Validate source file internally consistent. - String className = entry.getKey(); - CompilationUnit unit = entry.getValue(); - assertEquals(className, unit.getTypeName()); - - // Find the matching resource (and remove it). - if (unit.isGenerated()) { - assertTrue(generatedTypes.contains(className)); - assertNotNull(generatedTypes.remove(className)); - } else { - String partialPath = className.replace('.', '/') + ".java"; - assertTrue(sourceMap.containsKey(partialPath)); - // TODO: Validate the source file matches the resource. - assertNotNull(sourceMap.remove(partialPath)); - } - } - // The mutable sets should be empty now. - assertEquals(0, sourceMap.size()); - assertEquals(0, generatedTypes.size()); - } -} +} ======================================= --- /changes/jat/ihm/dev/core/test/com/google/gwt/dev/javac/JavaSourceParserTest.java Thu Jul 30 09:28:03 2009 +++ /changes/jat/ihm/dev/core/test/com/google/gwt/dev/javac/JavaSourceParserTest.java Thu Jul 30 15:12:48 2009 @@ -15,7 +15,12 @@ */ package com.google.gwt.dev.javac; -import junit.framework.TestCase; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JPrimitiveType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.NotFoundException; +import com.google.gwt.dev.javac.impl.MockJavaResource; import java.util.Arrays; import java.util.List; @@ -23,7 +28,33 @@ /** * Tests for {...@link JavaSourceParser}. */ -public class JavaSourceParserTest extends TestCase { +public class JavaSourceParserTest extends CompilationStateTestBase { + + private static final MockJavaResource FOO = new MockJavaResource("test.Foo") { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package test;\n"); + code.append("public class Foo {\n"); + code.append(" public String value(String a, int val) { return \"Foo\"; }\n"); + code.append("}\n"); + return code; + } + }; + + private static final MockJavaResource BAR = new MockJavaResource("test.Bar") { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package test;\n"); + code.append("public class Bar {\n"); + code.append(" public String value(String a, int val) { return \"Bar\"; }\n"); + code.append(" public String value(String a) { return \"Bar\"; }\n"); + code.append(" public String value(int val) { return \"Bar\"; }\n"); + code.append("}\n"); + return code; + } + }; /** * Test method for {...@link JavaSourceParser#getClassChain(java.lang.String)}. @@ -37,6 +68,41 @@ assertExpected(JavaSourceParser.getClassChain("test.test2.Foo$Bar$Baz"), "Foo", "Bar", "Baz"); } + + public void testLookup() throws NotFoundException { + JavaSourceParser parser = new JavaSourceParser(); + addGeneratedUnits(FOO); + addGeneratedUnits(BAR); + JClassType string = state.getTypeOracle().getType("java.lang.String"); + JClassType foo = state.getTypeOracle().getType("test.Foo"); + parser.addSourceForType(foo, FOO); + JClassType bar = state.getTypeOracle().getType("test.Bar"); + parser.addSourceForType(bar, BAR); + JMethod method = foo.getMethod("value", new JType[] { string, + JPrimitiveType.INT }); + String[] arguments = parser.getArguments(method); + assertNotNull(arguments); + assertEquals(2, arguments.length); + assertEquals("a", arguments[0]); + assertEquals("val", arguments[1]); + method = bar.getMethod("value", new JType[] { string, + JPrimitiveType.INT }); + arguments = parser.getArguments(method); + assertNotNull(arguments); + assertEquals(2, arguments.length); + assertEquals("a", arguments[0]); + assertEquals("val", arguments[1]); + method = bar.getMethod("value", new JType[] { JPrimitiveType.INT }); + arguments = parser.getArguments(method); + assertNotNull(arguments); + assertEquals(1, arguments.length); + assertEquals("val", arguments[0]); + method = bar.getMethod("value", new JType[] { string }); + arguments = parser.getArguments(method); + assertNotNull(arguments); + assertEquals(1, arguments.length); + assertEquals("a", arguments[0]); + } private void assertExpected(List<char[]> actual, String... expected) { if (actual.size() != expected.length) { --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---
