Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Java 9 project Jigsaw module-info.java files #800

Merged
merged 16 commits into from Mar 2, 2017
Merged

Support for Java 9 project Jigsaw module-info.java files #800

merged 16 commits into from Mar 2, 2017

Conversation

matozoid
Copy link
Contributor

@matozoid matozoid commented Feb 24, 2017

See #554.

This is a first draft. It seems to parse and print files quite well.

It has one big disadvantage: there is now a second grammar, and it contains a copy of the first. The differences are small, so that should be fixable with some trickery.

CSM or symbol solver support is not there yet.

I think the most interesting thing to look at is the unit test: https://github.com/javaparser/javaparser/pull/800/files#diff-32ad75ad8b271ad317da30653e5b9c83

@matozoid matozoid closed this Feb 24, 2017
@matozoid matozoid reopened this Feb 24, 2017
@matozoid
Copy link
Contributor Author

Build fails because the CSM wants me to implement module info. Can @ftomassetti help a bit?

@ftomassetti
Copy link
Member

Sure: to implement it or make CSM not complain?
In the latter case you may want to go to ConcreteSyntaxModel.java and changes these lines:

        List<String> unsupportedNodeClassNames = JavaParserMetaModel.getNodeMetaModels().stream()
                .filter(c -> !c.isAbstract() && !Comment.class.isAssignableFrom(c.getType()) && !concreteSyntaxModelByClass.containsKey(c.getType()))
                .map(nm -> nm.getType().getSimpleName())
                .collect(Collectors.toList());

Maybe you want to create a common ancestor for the ModuleInfo AST classes and put them in a set and make CSM skipping those

@matozoid
Copy link
Contributor Author

No no, to implement them. I can probably do it myself if I get the time, but I'm lazy. The PPV is already implemented.

@ftomassetti
Copy link
Member

You got your first PR :D

#1

I just sketch something immediately because in the week-end I will unable to do much coding :(

draft of CSM for the Module Info nodes
@ftomassetti
Copy link
Member

Do you think we will have many conflicts with #654 ?

@coveralls
Copy link

Coverage Status

Coverage decreased (-10.2%) to 50.812% when pulling 3f2f661 on matozoid:issue_554_part_1 into 8771897 on javaparser:master.

@matozoid
Copy link
Contributor Author

@ftomassetti - about conflicts: there is one extra parameter to the CU constructor, a bunch of extra nodes, and I've changed the names of the classes that JavaCC generates. So that may cause a lot of conflicts, but they should be simple to fix (mostly name changes.)

@matozoid
Copy link
Contributor Author

@ftomassetti - I've added a test to ModuleDeclarationTest, but it complains that ModuleProvidesStmt doesn't have a hasName method. Name is required, so I don't know why I would ask that. No time to figure it out right now.

@ftomassetti
Copy link
Member

@matozoid perfect. I do not plan to touch #654 anymore. If there are improvements to do (aside those that will emerge during code review) I will do as separate PRs

@matozoid matozoid changed the title Support for Java 9 project Jigsaw module-info.java files [DRAFT] Support for Java 9 project Jigsaw module-info.java files Feb 27, 2017
@matozoid
Copy link
Contributor Author

This works, but it isn't pretty. The problem is with the 10 additional keywords. They have to be specified in the list of keyword tokens. When you do so, all the following tokens will get their ID increased by 10 (because we just inserted 10 new ones.) Now we have a problem with the Concrete Syntax Model, which uses the token ID's from the normal grammar, and starts outputting the wrong tokens for the module info grammar.

The quick fix is to define 10 nonsense keywords in the normal grammar. They are named "qqqqqqqqqqqqqqqqqqqqqqq...."

I'll try to merge it back into one grammar, and redefine IDENTIFIER to match those ten keywords too... If that doesn't work either I'm out of options.

# Conflicts:
#	javaparser-core/src/main/java/com/github/javaparser/ast/visitor/GenericListVisitorAdapter.java
#	javaparser-core/src/main/java/com/github/javaparser/ast/visitor/ModifierVisitor.java
@matozoid
Copy link
Contributor Author

matozoid commented Feb 27, 2017

@ftomassetti what the heck does java.lang.UnsupportedOperationException: kept token(() vs ChildTextElement{foo} mean? I'm getting that from one of the unit tests.

@coveralls
Copy link

Coverage Status

Coverage decreased (-11.9%) to 51.007% when pulling b937b69 on matozoid:issue_554_part_1 into 52814c7 on javaparser:master.

@ftomassetti
Copy link
Member

It means that according to the difference it has calculated it is expecting to find a certain token and to keep it. It finds instead a token of a different type so it does not know what to do. Note that it is fine to find extra whitespace tokens or comments and they can be skip.

@matozoid
Copy link
Contributor Author

@ftomassetti it seems to be an EOL issue again, since travis doesn't complain and it build on my linux machine.

@ftomassetti
Copy link
Member

You mean the failures in Appveyor? Also, isn't surprising that coverage is down by almost 12%?

@matozoid
Copy link
Contributor Author

Coverage is explained by having a second copy of the grammar, which causes a second set of generated code.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.03%) to 62.963% when pulling de2adb5 on matozoid:issue_554_part_1 into 52814c7 on javaparser:master.

@matozoid
Copy link
Contributor Author

This was rather surprising: removed the second grammar, and made all the module-info keywords match as identifiers too.

String Identifier():
{
    String ret;
}
{
    // Make sure the module info keywords don't interfere with normal Java parsing by matching them as normal identifiers.
  (<MODULE> | <REQUIRES> | <TO> | <WITH> | <OPEN> | <OPENS> | <USES> | <EXPORTS> | <PROVIDES> | <TRANSITIVE> |
   <IDENTIFIER>) { ret = token.image; }
  { return ret; }
}

So this seems to work correctly!

@matozoid matozoid changed the title [DRAFT] Support for Java 9 project Jigsaw module-info.java files Support for Java 9 project Jigsaw module-info.java files Feb 27, 2017
@ftomassetti
Copy link
Member

Great!

Copy link

@gunnarmorling gunnarmorling left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @matozoid looks good to me after a quick look. Put two comments inline, mainly I was wondering why there is not a new version of the grammar file for Java 9.

@@ -37,7 +37,8 @@
VOLATILE,
SYNCHRONIZED,
NATIVE,
STRICTFP;
STRICTFP,
TRANSITIVE;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it should be treated as a regular modifier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put it there because JP matches all modifiers in every location where modifiers are allowed. The idea is to check if the correct modifiers are used by validating the AST, which is not implemented yet: #594 . Or did you mean something else?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see.

I expected this to resemble javax.lang.model.element.Modifier which doesn't contain TRANSITIVE. I.e. the "module-only" modifiers are kept separately there. There are separate enums for the different module instruction modifiers, e.g. here for Requires.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, that page says "mandated" is a modifier. I didn't see that in the JLS...

@@ -25,11 +25,11 @@ options {
JAVA_UNICODE_ESCAPE=true;
COMMON_TOKEN_ACTION=true;
JDK_VERSION = "1.8";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How comes that's art of java_1_8.jj? I'd have assumed you'd create a new file java_9.jj instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, looking at the changes in Java 9 it seems that it is a superset of 8, so we can keep a single grammar (much less maintenance work!) - but it should be renamed yes :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll not rename it right away because these comments might disappear...

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

@matozoid
Copy link
Contributor Author

@gunnarmorling thanks for the feedback - I was hoping you could check this branch out and try it on what you are thinking of using it for (if anything.)

@gunnarmorling
Copy link

I checked it out and parsed one test descriptor, all looking good.

I noticed though that JavaParser#parse(InputStream) doesn't like module descriptors as input, I had to use parseModuleInfo(). Is that expected?

@matozoid
Copy link
Contributor Author

@gunnarmorling currently it is, since it is a separate production, but I'll look into that tomorrow. Thanks for testing!

@gunnarmorling
Copy link

I see. Thanks a lot for working on this, @matozoid!

@coveralls
Copy link

Coverage Status

Coverage increased (+0.05%) to 62.983% when pulling a887dbe on matozoid:issue_554_part_1 into 52814c7 on javaparser:master.

tn = AnnotationTypeDeclaration(modifier) { types = add(types, tn); }
|
module = ModuleDeclaration(modifier)
|
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I did to make CompilationUnit() match all possible content. So the grammar thinks it's okay to mix classes, multiple module declarations, enums and annotation declarations in the same file. Yet again, a validator could check if a module declaration is mixed with type declarations and flag a warning or error.

@matozoid
Copy link
Contributor Author

Okay, the main JavaParser.parse(...) methods should now parse module declarations, and the grammar file no longer contains the java version, which wasn't helping anyone anyway.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.05%) to 62.983% when pulling a887dbe on matozoid:issue_554_part_1 into 52814c7 on javaparser:master.

@matozoid
Copy link
Contributor Author

I think this will do as a nice preview. If anything changes in the specs we can change it later.

Copy link
Member

@ftomassetti ftomassetti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, my only real concern is about the token type being IDENTIFIER when parsing identifiers that are keywords in the context of module declarations

new TreeStructureVisitorGenerator(sourceRoot).generate();
new ModifierVisitorGenerator(sourceRoot).generate();

new GetNodeListsGenerator(sourceRoot).generate();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why they are divided in two blocks? Is this worth a comment to explain what the two groups of generators represent?

public CloneVisitorGenerator(JavaParser javaParser, SourceRoot sourceRoot) {
super(javaParser, sourceRoot, "com.github.javaparser.ast.visitor", "CloneVisitor", "void", "A", true);
public CloneVisitorGenerator(SourceRoot sourceRoot) {
super(sourceRoot, "com.github.javaparser.ast.visitor", "CloneVisitor", "Visitable", "Object", true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I miss Kotlin named parameters :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True true, but if you really like those then the latest intellij can help you too :)

return result.getResult().get();
}
throw new ParseProblemException(result.getProblems());
return result.getResult().orElseThrow(() -> new ParseProblemException(result.getProblems()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

*/
default NormalAnnotationExpr addAnnotation(Class<? extends Annotation> clazz) {
default N addAnnotation(Class<? extends Annotation> clazz) {
((Node) this).tryAddImportToParentCompilationUnit(clazz);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this wise? Should the addImport be optional? What if the annotation is in the same package?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This functionality wasn't changed (what I did was correct the use of the "add" and "addAndGet" pattern) but it is a good idea: #803

public Visitable visit(ModuleUsesStmt n, A arg) {
Type type = (Type) n.getType().accept(this, arg);
Comment comment = n.getComment().map( s -> (Comment) s.accept(this, arg)).orElse(null);
if (type == null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: even in generated code I would really love to see the then statement between braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go submit a PR for your nitpicking :-P

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I have a better answer. The code in the generator is a little more readable because it doesn't contain the braces :)

@@ -64,17 +65,17 @@ private static CsmElement memberAnnotations() {
}

private static CsmElement annotations() {
return list(ObservableProperty.ANNOTATIONS, none(), none(), newline());
return list(ObservableProperty.ANNOTATIONS, space(), none(), newline());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am surprised this change does not cause tests to fail

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was surprised it was working at all :D

}

private static CsmElement typeParameters() {
return list(ObservableProperty.TYPE_PARAMETERS, CsmElement.sequence(CsmElement.comma(), CsmElement.space()), CsmElement.token(ASTParserConstants.LT),
CsmElement.sequence(CsmElement.token(ASTParserConstants.GT), CsmElement.space()));
return list(ObservableProperty.TYPE_PARAMETERS, CsmElement.sequence(CsmElement.comma(), CsmElement.space()), CsmElement.token(GeneratedJavaParserConstants.LT),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could use static imports for the token constants

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a big fan of static imports, and I thought you were not using them here because there are constants coming in from several sources. PR please :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure whether you clicked that option or not when creating this PR, but since recently one can have other contributors push to the same PR. I.e. @ftomassetti could add this kind of changes right to this PR (it's a checkbox when submitting a PR). Found it quite useful myself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed it completely, but that's good to know!

}
{
// Make sure the module info keywords don't interfere with normal Java parsing by matching them as normal identifiers.
(<MODULE> | <REQUIRES> | <TO> | <WITH> | <OPEN> | <OPENS> | <USES> | <EXPORTS> | <PROVIDES> | <TRANSITIVE> |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case what would be the token type? If the topken type is not IDENTIFIER we could run into issues with the CSM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I was worried about too, but the tests kept running. Would you like to suggest a test for this?

Anyway, the fix would be to override the token type to IDENTIFIER right here, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ftomassetti - do you have an idea how to test this? It is the last thing before I pull this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would:

  • parse something that has a name
  • change it to a name corresponding to a module keyword
  • print it using lexical preservation

Do the viceversa (starting from something having a name like "requires").

These tests would be very similar to the ones in the lexicalpreservation package

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ftomassetti I added the following tests and they work - am I doing this right?

    @Test
    public void printAModuleInfoSpecificKeywordUsedAsIdentifier1() {
        considerCode("class module { }");

        cu.getClassByName("module").get().setName("xyz");

        assertEquals("class xyz { }", lpp.print(cu));
    }

    @Test
    public void printAModuleInfoSpecificKeywordUsedAsIdentifier2() {
        considerCode("class xyz { }");

        cu.getClassByName("xyz").get().setName("module");

        assertEquals("class module { }", lpp.print(cu));
    }

@ftomassetti
Copy link
Member

+1 from me: the tests in LexicalPreservingPrinterTest.java are exactly what I meant

@matozoid matozoid merged commit 20dbe9d into javaparser:master Mar 2, 2017
@matozoid matozoid deleted the issue_554_part_1 branch March 2, 2017 18:17
@matozoid
Copy link
Contributor Author

matozoid commented Mar 2, 2017

Thank you!

@matozoid matozoid added this to the next release milestone Mar 2, 2017
@gunnarmorling
Copy link

Awesome, thanks a lot for making it happen, @matozoid and @ftomassetti! Any idea when we can get it in a release?

@matozoid
Copy link
Contributor Author

matozoid commented Mar 2, 2017

Yes, next Sunday. This project releases every Sunday unless something gets really in the way.

@matozoid
Copy link
Contributor Author

matozoid commented Mar 2, 2017

I'm not sure what you want to do it with it, but remember that this is only partial Java 9 support :) Here's the rest: Java 9 support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants