Bug 257528 - An API to incrementaly generate compilation units for binding resolution environment
Summary: An API to incrementaly generate compilation units for binding resolution envi...
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.4.2   Edit
Hardware: PC Windows XP
: P3 enhancement (vote)
Target Milestone: 3.5 M6   Edit
Assignee: Jerome Lanneluc CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2008-12-04 08:33 EST by Satish C Gupta CLA
Modified: 2010-02-06 02:36 EST (History)
3 users (show)

See Also:


Attachments
Corresponding implementation and tests (11.00 KB, patch)
2008-12-12 09:27 EST, Jerome Lanneluc CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Satish C Gupta CLA 2008-12-04 08:33:54 EST
Currently JDT aims to perform semantic analysis on Java source files, and expects all Java sources (at least the source stubs - compilation units with empty functions - for classes other than the one being compiled) to be present. This constraint impacts scalability in certain situations. Here is an example.

Consider that there is a UML model which is used to define structural elements (class, interface, method, attribute), but the method-body is embedded as Java code snippet in the model. So UML model defines complete system except the method bodies. Say, it is required to validate and translate these Java code snippets in the context of the UML model. These are the two most common tasks:

1)
When a user edits an embedded Java code snippet, it is required to validate the Java code snippet using JDT. The expectation is that JDT semantic analysis should report errors in the context of the UML model. For example, if the code snippet has a local variable v of type T, and v.foo() is being called in the code snippet, and there is no foo defined in T or any of its super class, then an error must be reported by the editor). Essentially any failure in binding resolution needs to be reported.

Currently, JDT requires that all compilation units referred from code snippet should exist; else bindings will not be resolved while parsing. Since it can not be knows what are the types referred from a code snippet (till it is parsed and binding resolution is attempted), it requires to generate compilation units (with just function stubs) for all classes defined in the model. If the code snippet refers to, say, 10 types, and there are 1000 classes in the model, this constrain will make editing inefficient and un-scalable. 

An API which somehow passes the control back to JDT-client when a required compilation unit is missing/unresolved, and the client generates the compilation unit (with only method stubs), and returns for JDT to resume its semantic analysis, will require to generate only the classes that are referred from the Java code snippet, and that will make it scalable.

2)
Second requirement is while translation, when a binding is resolved to class/method/field definition (an ASTNode), it is needed to map that ASTNode to corresponding structural definition in the model.

Currently, it can be achieved by generating comment for each structural element while generating the class stub and embedding a unique ID in the comment. While translation, the comment can be extracted, parsed and ID retrieved. However, if there can be a new annotation type (say @id("string")) for definitions, then it will easier/cleaner to extract the id.
Comment 1 Jerome Lanneluc CLA 2008-12-05 05:21:42 EST
For 2) since this is a separate request, could you please enter a separate bug report.

For 1), would these 2 APIs on org.eclipse.jdt.core.WorkingCopyOwner work for you?

/**
 * Hook to add more types that are not available through normal lookup (i.e. 
 * types that are not available on the project's classpath, nor through the 
 * working copies of this owner).
 * <p>Example of use:
 * <pre>
 * WorkingCopyOwner owner = new WorkingCopyOwner() {
 *   public String findSource(String typeName, String packageName) {
 *     if ("to.be".equals(packageName) && "Generated".equals(typeName)) {
 *       return
 *         "package to.be;\n" +
 *         "public class Generated {\n" +
 *         "}";
 *     }
 *     return super.findSource(typeName, packageName);
 *   }
 *   public boolean isPackage(String[] pkg) {
 *     switch (pkg.length) {
 *     case 1:
 *       return "to".equals(pkg[0]);
 *     case 2:
 *       return "to".equals(pkg[0]) && "be".equals(pkg[1]);
 *     }
 *     return false;
 *   }
 * };
 * ICompilationUnit workingCopy = 
 *   getWorkingCopy(
 *     "/P/X.java", 
 *     "public class X extends to.be.Generated {\n" +
 *     "}");
 * ASTParser parser = ASTParser.newParser(AST.JLS3);
 * parser.setSource(workingCopy);
 * parser.setResolveBindings(true);
 * parser.setWorkingCopyOwner(owner);
 * CompilationUnit cu = (CompilationUnit) parser.createAST(null);
 * assert cu.getProblems().length == 0;
 * </pre>
 * </p>
 * 
 * @param typeName the simple name of the type to lookup
 * @param packageName the dot-separated name of the package of type
 * @see #isPackage(String[])
 * @since 3.5
 */
public String findSource(String typeName, String packageName)

/**
 * Hook to add more packages that are not available through normal lookup (i.e.
 * packages that are not available on the project's classpath, nor through the 
 * working copies of this owner).
 * 
 * @param pkg the segments of a package to lookup
 * @see #findSource(String, String)
 * @since 3.5
 */
public boolean isPackage(String[] pkg)
Comment 2 Philipe Mulet CLA 2008-12-05 08:00:07 EST
Re: public String findSource(String typeName, String packageName)

It should answer 'null' to indicate deferring to standard lookup.
Should clarify that this type lookup could be answering CU source (could contain multiple types).

Re: public boolean isPackage(String[] pkg)

This should be a 3-way question. YES / NO / DUNNO. Only if DUNNO would it defer to standard lookup to find out.
Comment 3 Philipe Mulet CLA 2008-12-05 11:24:31 EST
Note: my previous comment assumed these new hooks were called before doing standard lookup. Talking with Jerome, it wasn't the intent.

I think it should do hook pre-lookup still to match the mode where working copies are being supplied to hide existing resources.
Comment 4 Satish C Gupta CLA 2008-12-06 00:17:19 EST
The suggested API seems sufficient, except it is not clear which getWorkingCopy() (which class has this function) is being called in:

 * ICompilationUnit workingCopy = 
 *   getWorkingCopy(
 *     "/P/X.java", 
 *     "public class X extends to.be.Generated {\n" +
 *     "}");

I am most probably missing something here, but I couldn't locate a getWorkingCopy() that takes a unit-name and source string and creates a ICompilationUnit.


Looking at Philippe's I realize that there are two scenarios:

1) 
Ability to supply classes not yet found by standard lookup: This is what my current need is. When a class to.be.Generated (in the API documentation in comment #1 ) is looked up and found non-existing, I need a hook to be able to generate it. However, when the class to.be.Generated is looked up again, as in processing following, the standard lookup should be able to find to.be.Generated without requiring to.be.Generated to be generated again:

ICompilationUnit workingCopy = 
   getWorkingCopy(
     "/P/X.java", 
     "public class X extends to.be.Generated {\n" +
     "    public static class FuzzyX extends to.be.Generated {\n" +
     "    }" +
     "}");

2)
Ability to supply classes that hide classes visible in standard lookup: I don't need this ability at present, but I can see that it can be pretty useful in certain situations.

I guess the question is how to indicate JDT to call findSource() before or after its standard lookup.
Comment 5 Jerome Lanneluc CLA 2008-12-12 09:23:30 EST
(In reply to comment #4)
> The suggested API seems sufficient, except it is not clear which
> getWorkingCopy() 
This was meant to be pseudo-code (ie. there are many ways to create a working copy, but this is out of the scope of this API). I tried to clarify that in the new version of the API below.

Also, I tried to clarify when the findSource() and isPackage() methods are called.

/**
 * Returns the source of the compilation unit that defines the given type in
 * the given package, or <code>null</code> if the type is unknown to this
 * owner.
 * <p>This method is called before the normal lookup (i.e. before looking 
 * at the project's classpath and before looking at the working copies of this 
 * owner.)</p>
 * <p>This allows to provide types that are not normally available, or to hide 
 * types that would normally be available by returning an empty source for 
 * the given type and package.</p>
 * <p>Example of use:
 * <pre>
 * WorkingCopyOwner owner = new WorkingCopyOwner() {
 *   public String findSource(String typeName, String packageName) {
 *     if ("to.be".equals(packageName) && "Generated".equals(typeName)) {
 *       return
 *         "package to.be;\n" +
 *         "public class Generated {\n" +
 *         "}";
 *     }
 *     return super.findSource(typeName, packageName);
 *   }
 *   public boolean isPackage(String[] pkg) {
 *     switch (pkg.length) {
 *     case 1:
 *       return "to".equals(pkg[0]);
 *     case 2:
 *       return "to".equals(pkg[0]) && "be".equals(pkg[1]);
 *     }
 *     return false;
 *   }
 * };
 * // Working copy on X.java with the following contents:
 * //    public class X extends to.be.Generated {
 * //    }
 * ICompilationUnit workingCopy = ... 
 * ASTParser parser = ASTParser.newParser(AST.JLS3);
 * parser.setSource(workingCopy);
 * parser.setResolveBindings(true);
 * parser.setWorkingCopyOwner(owner);
 * CompilationUnit cu = (CompilationUnit) parser.createAST(null);
 * assert cu.getProblems().length == 0;
 * </pre>
 * </p>
 * 
 * @param typeName the simple name of the type to lookup
 * @param packageName the dot-separated name of the package of type
 * @return the source of the compilation unit that defines the given type in
 * the given package, or <code>null</code> if the type is unknown
 * @see #isPackage(String[])
 * @since 3.5
 */
public String findSource(String typeName, String packageName) {
	return null;
}

/**
 * Returns whether the given package segments represent a package.
 * <p>This method is called before the normal lookup (i.e. before looking 
 * at the project's classpath and before looking at the working copies of this 
 * owner.)</p>
 * <p>This allows to provide packages that are not normally available.</p>
 * <p>If <code>false</code> is returned, then normal lookup is used on 
 * this package.</p>
 * 
 * @param pkg the segments of a package to lookup
 * @return whether the given package segments represent a package.
 * @see #findSource(String, String)
 * @since 3.5
 */
public boolean isPackage(String[] pkg) {
	return false;
}
Comment 6 Jerome Lanneluc CLA 2008-12-12 09:27:42 EST
Created attachment 120323 [details]
Corresponding implementation and tests
Comment 7 Satish C Gupta CLA 2009-03-03 09:29:08 EST
The API provided in the patch is sufficient for me.
Thanks,
+satish
Comment 8 Jerome Lanneluc CLA 2009-03-04 04:33:00 EST
API, implementation and tests released for 3.5M6
Comment 9 Olivier Thomann CLA 2009-03-10 09:55:33 EDT
Verified for 3.5M6 using I20090310-0100 (looking at source code).