diff --git a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml
index 2668f85de..cdee0e307 100644
--- a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml
+++ b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml
@@ -29,7 +29,7 @@ under the License.
Maven Integration Test :: gh-944-exclude-source-directory
- Test plugin-plugin, plugin.xml descriptor - shouldn't contain explicitly excluded source directories
+ Test plugin-plugin, plugin.xml descriptor - shouldn't contain explicitly excluded source directories
@@ -96,8 +96,8 @@ under the License.
@project.version@
-
- ${project.build.directory}/generated-sources/annotations
+
+ ${project.basedir}/src/main/java
prefix
diff --git a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java
index c5290edd5..50f6463c6 100644
--- a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java
+++ b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java
@@ -27,8 +27,6 @@
@Immutable
@Modifiable
public interface SomeGeneratedModel {
- // Triggers https://github.com/paul-hammant/qdox/issues/287 on the generated code when QDox tries
- // to scan it.
@Nullable
List<@Nullable String> getThings();
}
diff --git a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/verify.groovy b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/verify.groovy
new file mode 100644
index 000000000..41dd1b13f
--- /dev/null
+++ b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/verify.groovy
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import groovy.xml.XmlParser
+
+File descriptorFile = new File(basedir, "target/classes/META-INF/maven/plugin.xml")
+assert descriptorFile.isFile()
+
+def pluginDescriptor = new XmlParser().parse(descriptorFile)
+def mojo = pluginDescriptor.mojos.mojo.find { it.goal.text() == "first" }
+
+assert mojo != null
+assert mojo.description.text() == ""
+
+return true
diff --git a/maven-plugin-plugin/src/it/javaparser-generated-source/invoker.properties b/maven-plugin-plugin/src/it/javaparser-generated-source/invoker.properties
new file mode 100644
index 000000000..2f680d3f4
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-generated-source/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+invoker.goals = process-classes
diff --git a/maven-plugin-plugin/src/it/javaparser-generated-source/pom.xml b/maven-plugin-plugin/src/it/javaparser-generated-source/pom.xml
new file mode 100644
index 000000000..bc7beb742
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-generated-source/pom.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.javaparser-generated-source
+ javaparser-generated-source
+ 1.0-SNAPSHOT
+ maven-plugin
+
+ Maven Integration Test :: JavaParser Generated Source
+ Checks that JavaParser scans generated sources containing type-use annotations.
+
+
+ UTF-8
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ @maven3Version@
+ provided
+
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ @project.version@
+ provided
+
+
+
+ org.immutables
+ value
+ 2.12.0
+ provided
+
+
+
+ org.jspecify
+ jspecify
+ 1.0.0
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ @version.maven-compiler-plugin@
+
+ 1.8
+ 1.8
+
+
+
+ org.immutables
+ value
+ 2.12.0
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ @project.version@
+
+ prefix
+
+
+
+
+
diff --git a/maven-plugin-plugin/src/it/javaparser-generated-source/src/main/java/org/apache/maven/plugin/coreit/FirstMojo.java b/maven-plugin-plugin/src/it/javaparser-generated-source/src/main/java/org/apache/maven/plugin/coreit/FirstMojo.java
new file mode 100644
index 000000000..df868e1e7
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-generated-source/src/main/java/org/apache/maven/plugin/coreit/FirstMojo.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.maven.plugin.coreit;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+/** A Mojo that uses the generated model. */
+@Mojo(name = "first")
+public class FirstMojo extends AbstractMojo {
+
+ @Parameter
+ private ModifiableSomeGeneratedModel model;
+
+ @Override
+ public void execute() {
+ // Nothing to do.
+ }
+}
diff --git a/maven-plugin-plugin/src/it/javaparser-generated-source/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java b/maven-plugin-plugin/src/it/javaparser-generated-source/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java
new file mode 100644
index 000000000..50f6463c6
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-generated-source/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.maven.plugin.coreit;
+
+import java.util.List;
+
+import org.immutables.value.Value.Immutable;
+import org.immutables.value.Value.Modifiable;
+import org.jspecify.annotations.Nullable;
+
+@Immutable
+@Modifiable
+public interface SomeGeneratedModel {
+ @Nullable
+ List<@Nullable String> getThings();
+}
diff --git a/maven-plugin-plugin/src/it/javaparser-platform-classloader/base/pom.xml b/maven-plugin-plugin/src/it/javaparser-platform-classloader/base/pom.xml
new file mode 100644
index 000000000..66714836d
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-platform-classloader/base/pom.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ 4.0.0
+
+
+ org.apache.maven.its.javaparser-platform-classloader
+ javaparser-platform-classloader
+ 1.0-SNAPSHOT
+
+
+ base
+
diff --git a/maven-plugin-plugin/src/it/javaparser-platform-classloader/base/src/main/java/org/apache/maven/plugin/coreit/PlatformType.java b/maven-plugin-plugin/src/it/javaparser-platform-classloader/base/src/main/java/org/apache/maven/plugin/coreit/PlatformType.java
new file mode 100644
index 000000000..b7551f33e
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-platform-classloader/base/src/main/java/org/apache/maven/plugin/coreit/PlatformType.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.maven.plugin.coreit;
+
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverPropertyInfo;
+import java.sql.SQLException;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+/** A reactor class whose interface is loaded by the platform classloader on Java 9 and later. */
+public class PlatformType implements Driver {
+
+ @Override
+ public Connection connect(String url, Properties info) throws SQLException {
+ return null;
+ }
+
+ @Override
+ public boolean acceptsURL(String url) throws SQLException {
+ return false;
+ }
+
+ @Override
+ public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
+ return new DriverPropertyInfo[0];
+ }
+
+ @Override
+ public int getMajorVersion() {
+ return 1;
+ }
+
+ @Override
+ public int getMinorVersion() {
+ return 0;
+ }
+
+ @Override
+ public boolean jdbcCompliant() {
+ return false;
+ }
+
+ @Override
+ public Logger getParentLogger() {
+ return Logger.getGlobal();
+ }
+}
diff --git a/maven-plugin-plugin/src/it/javaparser-platform-classloader/invoker.properties b/maven-plugin-plugin/src/it/javaparser-platform-classloader/invoker.properties
new file mode 100644
index 000000000..e6ff81c3e
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-platform-classloader/invoker.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+invoker.java.version = 9+
+invoker.goals = clean process-classes
diff --git a/maven-plugin-plugin/src/it/javaparser-platform-classloader/plugin/pom.xml b/maven-plugin-plugin/src/it/javaparser-platform-classloader/plugin/pom.xml
new file mode 100644
index 000000000..9ddde97f6
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-platform-classloader/plugin/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+ 4.0.0
+
+
+ org.apache.maven.its.javaparser-platform-classloader
+ javaparser-platform-classloader
+ 1.0-SNAPSHOT
+
+
+ plugin
+ maven-plugin
+
+
+
+ ${project.groupId}
+ base
+ ${project.version}
+
+
+ org.apache.maven
+ maven-plugin-api
+ @maven3Version@
+ provided
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ @project.version@
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ @project.version@
+
+ platform-classloader
+
+
+
+
+
diff --git a/maven-plugin-plugin/src/it/javaparser-platform-classloader/plugin/src/main/java/org/apache/maven/plugin/coreit/HelloMojo.java b/maven-plugin-plugin/src/it/javaparser-platform-classloader/plugin/src/main/java/org/apache/maven/plugin/coreit/HelloMojo.java
new file mode 100644
index 000000000..b516e63ca
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-platform-classloader/plugin/src/main/java/org/apache/maven/plugin/coreit/HelloMojo.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.maven.plugin.coreit;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.Mojo;
+
+/** Links to {@link PlatformType}. */
+@Mojo(name = "hello")
+public class HelloMojo extends AbstractMojo {
+
+ @Override
+ public void execute() {
+ getLog().info("Hello");
+ }
+}
diff --git a/maven-plugin-plugin/src/it/javaparser-platform-classloader/pom.xml b/maven-plugin-plugin/src/it/javaparser-platform-classloader/pom.xml
new file mode 100644
index 000000000..a53ea4db5
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-platform-classloader/pom.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.javaparser-platform-classloader
+ javaparser-platform-classloader
+ 1.0-SNAPSHOT
+ pom
+
+ Maven Integration Test :: JavaParser Platform Classloader
+ Checks source resolution for reactor classes that use JDK platform modules.
+
+
+ base
+ plugin
+
+
+
+ UTF-8
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ @version.maven-compiler-plugin@
+
+ 8
+ 8
+
+
+
+
+
+
diff --git a/maven-plugin-plugin/src/it/javaparser-platform-classloader/verify.groovy b/maven-plugin-plugin/src/it/javaparser-platform-classloader/verify.groovy
new file mode 100644
index 000000000..792cf4f24
--- /dev/null
+++ b/maven-plugin-plugin/src/it/javaparser-platform-classloader/verify.groovy
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import groovy.xml.XmlParser
+
+File descriptorFile = new File( basedir, "plugin/target/classes/META-INF/maven/plugin.xml" )
+assert descriptorFile.isFile()
+
+def pluginDescriptor = new XmlParser().parse( descriptorFile )
+def mojo = pluginDescriptor.mojos.mojo.find { it.goal.text() == "hello" }
+
+assert mojo != null
+assert mojo.description.text().contains( "PlatformType" )
+
+File buildLog = new File( basedir, "build.log" )
+assert buildLog.isFile()
+assert !buildLog.text.contains( "Unresolvable link in javadoc tag with value PlatformType" )
+
+return true
diff --git a/maven-plugin-plugin/src/it/mplugin-421/invoker.properties b/maven-plugin-plugin/src/it/mplugin-421/invoker.properties
new file mode 100644
index 000000000..a5ed592bd
--- /dev/null
+++ b/maven-plugin-plugin/src/it/mplugin-421/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+invoker.goals = clean verify
diff --git a/maven-plugin-plugin/src/it/mplugin-421/pom.xml b/maven-plugin-plugin/src/it/mplugin-421/pom.xml
new file mode 100644
index 000000000..2321a47f4
--- /dev/null
+++ b/maven-plugin-plugin/src/it/mplugin-421/pom.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.mplugin-421
+ mplugin-421
+ 1.0-SNAPSHOT
+ maven-plugin
+
+ Maven Integration Test :: mplugin-421
+ Checks that comment-only Java sources do not prevent plugin descriptor generation.
+
+
+ UTF-8
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ @maven3Version@
+ provided
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ @project.version@
+ provided
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ @version.maven-compiler-plugin@
+
+ 1.8
+ 1.8
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ @project.version@
+
+ mplugin421
+
+
+
+
+
diff --git a/maven-plugin-plugin/src/it/mplugin-421/src/main/java/org/apache/maven/plugin/coreit/CommentOnly.java b/maven-plugin-plugin/src/it/mplugin-421/src/main/java/org/apache/maven/plugin/coreit/CommentOnly.java
new file mode 100644
index 000000000..7d50ac216
--- /dev/null
+++ b/maven-plugin-plugin/src/it/mplugin-421/src/main/java/org/apache/maven/plugin/coreit/CommentOnly.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+// This file intentionally contains no Java declaration.
diff --git a/maven-plugin-plugin/src/it/mplugin-421/src/main/java/org/apache/maven/plugin/coreit/HelloMojo.java b/maven-plugin-plugin/src/it/mplugin-421/src/main/java/org/apache/maven/plugin/coreit/HelloMojo.java
new file mode 100644
index 000000000..c2b7a2fa0
--- /dev/null
+++ b/maven-plugin-plugin/src/it/mplugin-421/src/main/java/org/apache/maven/plugin/coreit/HelloMojo.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.maven.plugin.coreit;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.Mojo;
+
+/** Says hello. */
+@Mojo(name = "hello")
+public class HelloMojo extends AbstractMojo {
+
+ @Override
+ public void execute() {
+ getLog().info("Hello");
+ }
+}
diff --git a/maven-plugin-plugin/src/it/mplugin-421/verify.groovy b/maven-plugin-plugin/src/it/mplugin-421/verify.groovy
new file mode 100644
index 000000000..df29f2e15
--- /dev/null
+++ b/maven-plugin-plugin/src/it/mplugin-421/verify.groovy
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import groovy.xml.XmlParser
+
+File descriptorFile = new File( basedir, "target/classes/META-INF/maven/plugin.xml" )
+assert descriptorFile.isFile()
+
+def pluginDescriptor = new XmlParser().parse( descriptorFile )
+def mojo = pluginDescriptor.mojos.mojo.find { it.goal.text() == "hello" }
+
+assert mojo != null
+assert mojo.implementation.text() == "org.apache.maven.plugin.coreit.HelloMojo"
+assert mojo.description.text() == "Says hello."
+
+return true
diff --git a/maven-plugin-tools-annotations/pom.xml b/maven-plugin-tools-annotations/pom.xml
index 090da5902..0b6fb6b00 100644
--- a/maven-plugin-tools-annotations/pom.xml
+++ b/maven-plugin-tools-annotations/pom.xml
@@ -97,8 +97,8 @@
jsoup
- com.thoughtworks.qdox
- qdox
+ com.github.javaparser
+ javaparser-symbol-solver-core
diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java
index b44f3c372..34357c857 100644
--- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java
+++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java
@@ -23,9 +23,9 @@
import javax.inject.Singleton;
import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -42,14 +42,16 @@
import java.util.TreeSet;
import java.util.stream.Collectors;
-import com.thoughtworks.qdox.JavaProjectBuilder;
-import com.thoughtworks.qdox.library.SortedClassLibraryBuilder;
-import com.thoughtworks.qdox.model.DocletTag;
-import com.thoughtworks.qdox.model.JavaAnnotatedElement;
-import com.thoughtworks.qdox.model.JavaClass;
-import com.thoughtworks.qdox.model.JavaField;
-import com.thoughtworks.qdox.model.JavaMember;
-import com.thoughtworks.qdox.model.JavaMethod;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.MethodDeclaration;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.ast.nodeTypes.NodeWithJavadoc;
+import com.github.javaparser.ast.type.ClassOrInterfaceType;
+import com.github.javaparser.javadoc.Javadoc;
+import com.github.javaparser.javadoc.JavadocBlockTag;
+import com.github.javaparser.resolution.UnsolvedSymbolException;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.apache.maven.plugin.descriptor.InvalidParameterException;
@@ -206,9 +208,6 @@ public List execute(PluginToolsRequest request)
request.setRequiredJavaVersion(requiredJavaVersion);
}
}
- JavaProjectBuilder builder = scanJavadoc(request, mojoAnnotatedClasses.values());
- Map javaClassesMap = discoverClasses(builder);
-
final JavadocLinkGenerator linkGenerator;
if (request.getInternalJavadocBaseUrl() != null
|| (request.getExternalJavadocBaseUrls() != null
@@ -222,7 +221,12 @@ public List execute(PluginToolsRequest request)
linkGenerator = null;
}
- populateDataFromJavadoc(builder, mojoAnnotatedClasses, javaClassesMap, linkGenerator);
+ try (JavaSourceModel sourceModel = scanJavadoc(request, mojoAnnotatedClasses.values())) {
+ Map> javaClassesMap = discoverClasses(sourceModel);
+ populateDataFromJavadoc(sourceModel, mojoAnnotatedClasses, javaClassesMap, linkGenerator);
+ } catch (IOException e) {
+ throw new ExtractionException("Could not parse Java sources: " + e.getMessage(), e);
+ }
return toMojoDescriptors(mojoAnnotatedClasses, request.getPluginDescriptor());
}
@@ -242,9 +246,8 @@ private Map scanAnnotations(PluginToolsRequest reque
return result;
}
- private JavaProjectBuilder scanJavadoc(
- PluginToolsRequest request, Collection mojoAnnotatedClasses)
- throws ExtractionException {
+ private JavaSourceModel scanJavadoc(PluginToolsRequest request, Collection mojoAnnotatedClasses)
+ throws ExtractionException, IOException {
// found artifact from reactors to scan sources
// we currently only scan sources from reactors
List mavenProjects = new ArrayList<>();
@@ -252,9 +255,10 @@ private JavaProjectBuilder scanJavadoc(
// if we need to scan sources from external artifacts
Set externalArtifacts = new HashSet<>();
- JavaProjectBuilder builder = new JavaProjectBuilder(new SortedClassLibraryBuilder());
- builder.setEncoding(request.getEncoding());
- extendJavaProjectBuilder(request, builder, request.getProject());
+ Charset encoding =
+ request.getEncoding() == null ? StandardCharsets.UTF_8 : Charset.forName(request.getEncoding());
+ JavaSourceModel sourceModel = new JavaSourceModel(encoding);
+ extendJavaSourceModel(request, sourceModel, request.getProject());
for (MojoAnnotatedClass mojoAnnotatedClass : mojoAnnotatedClasses) {
if (Objects.equals(
@@ -282,17 +286,18 @@ private JavaProjectBuilder scanJavadoc(
for (Artifact artifact : externalArtifacts) {
// parameter for test-sources too ?? olamy I need that for it test only
if (StringUtils.equalsIgnoreCase("tests", artifact.getClassifier())) {
- extendJavaProjectBuilderWithSourcesJar(builder, artifact, request, "test-sources");
+ extendJavaSourceModelWithSourcesJar(sourceModel, artifact, request, "test-sources");
} else {
- extendJavaProjectBuilderWithSourcesJar(builder, artifact, request, "sources");
+ extendJavaSourceModelWithSourcesJar(sourceModel, artifact, request, "sources");
}
}
for (MavenProject mavenProject : mavenProjects) {
- extendJavaProjectBuilder(request, builder, mavenProject);
+ extendJavaSourceModel(request, sourceModel, mavenProject);
}
- return builder;
+ sourceModel.parse();
+ return sourceModel;
}
private boolean isMojoAnnnotatedClassCandidate(MojoAnnotatedClass mojoAnnotatedClass) {
@@ -303,281 +308,191 @@ private boolean isMojoAnnnotatedClassCandidate(MojoAnnotatedClass mojoAnnotatedC
* from sources scan to get @since and @deprecated and description of classes and fields.
*/
protected void populateDataFromJavadoc(
- JavaProjectBuilder javaProjectBuilder,
+ JavaSourceModel sourceModel,
Map mojoAnnotatedClasses,
- Map javaClassesMap,
+ Map> javaClassesMap,
JavadocLinkGenerator linkGenerator) {
-
for (Map.Entry entry : mojoAnnotatedClasses.entrySet()) {
- JavaClass javaClass = javaClassesMap.get(entry.getKey());
+ TypeDeclaration> javaClass = javaClassesMap.get(entry.getKey());
if (javaClass == null) {
continue;
}
- // populate class-level content
- MojoAnnotationContent mojoAnnotationContent = entry.getValue().getMojo();
- if (mojoAnnotationContent != null) {
+ MojoAnnotationContent mojo = entry.getValue().getMojo();
+ if (mojo != null) {
JavaClassConverterContext context = new JavaClassConverterContext(
- javaClass, javaProjectBuilder, mojoAnnotatedClasses, linkGenerator, javaClass.getLineNumber());
- mojoAnnotationContent.setDescription(getDescriptionFromElement(javaClass, context));
-
- DocletTag since = findInClassHierarchy(javaClass, "since");
- if (since != null) {
- mojoAnnotationContent.setSince(getRawValueFromTaglet(since, context));
- }
-
- DocletTag deprecated = findInClassHierarchy(javaClass, "deprecated");
- if (deprecated != null) {
- mojoAnnotationContent.setDeprecated(getRawValueFromTaglet(deprecated, context));
- }
+ javaClass, sourceModel, mojoAnnotatedClasses, linkGenerator, lineNumber(javaClass));
+ mojo.setDescription(getDescriptionFromElement(javaClass, context));
+ findInClassHierarchy(sourceModel, javaClass, "since")
+ .ifPresent(tag -> mojo.setSince(getRawValueFromTaglet(tag, context)));
+ findInClassHierarchy(sourceModel, javaClass, "deprecated")
+ .ifPresent(tag -> mojo.setDeprecated(getRawValueFromTaglet(tag, context)));
}
- Map fieldsMap = extractFieldsAnnotations(javaClass, javaClassesMap);
- Map methodsMap = extractMethodsAnnotations(javaClass, javaClassesMap);
-
- // populate parameters
+ Map fields = extractFields(sourceModel, javaClass);
+ Map methods = extractMethods(sourceModel, javaClass);
Map parameters =
- getParametersParentHierarchy(entry.getValue(), mojoAnnotatedClasses);
- parameters = new TreeMap<>(parameters);
+ new TreeMap<>(getParametersParentHierarchy(entry.getValue(), mojoAnnotatedClasses));
for (Map.Entry parameter : parameters.entrySet()) {
- JavaAnnotatedElement element;
- if (parameter.getValue().isAnnotationOnMethod()) {
- element = methodsMap.get(parameter.getKey());
- } else {
- element = fieldsMap.get(parameter.getKey());
- }
-
- if (element == null) {
- continue;
- }
-
- JavaClassConverterContext context = new JavaClassConverterContext(
- javaClass, ((JavaMember) element).getDeclaringClass(),
- javaProjectBuilder, mojoAnnotatedClasses,
- linkGenerator, element.getLineNumber());
- ParameterAnnotationContent parameterAnnotationContent = parameter.getValue();
- parameterAnnotationContent.setDescription(getDescriptionFromElement(element, context));
-
- DocletTag deprecated = element.getTagByName("deprecated");
- if (deprecated != null) {
- parameterAnnotationContent.setDeprecated(getRawValueFromTaglet(deprecated, context));
- }
-
- DocletTag since = element.getTagByName("since");
- if (since != null) {
- parameterAnnotationContent.setSince(getRawValueFromTaglet(since, context));
+ SourceMember member = parameter.getValue().isAnnotationOnMethod()
+ ? methods.get(parameter.getKey())
+ : fields.get(parameter.getKey());
+ if (member != null) {
+ populateMemberJavadoc(
+ javaClass, member, parameter.getValue(), sourceModel, mojoAnnotatedClasses, linkGenerator);
}
}
- // populate components
- Map components =
- entry.getValue().getComponents();
- for (Map.Entry component : components.entrySet()) {
- JavaAnnotatedElement element = fieldsMap.get(component.getKey());
- if (element == null) {
- continue;
- }
-
- JavaClassConverterContext context = new JavaClassConverterContext(
- javaClass, ((JavaMember) element).getDeclaringClass(),
- javaProjectBuilder, mojoAnnotatedClasses,
- linkGenerator, javaClass.getLineNumber());
- ComponentAnnotationContent componentAnnotationContent = component.getValue();
- componentAnnotationContent.setDescription(getDescriptionFromElement(element, context));
-
- DocletTag deprecated = element.getTagByName("deprecated");
- if (deprecated != null) {
- componentAnnotationContent.setDeprecated(getRawValueFromTaglet(deprecated, context));
- }
-
- DocletTag since = element.getTagByName("since");
- if (since != null) {
- componentAnnotationContent.setSince(getRawValueFromTaglet(since, context));
+ for (Map.Entry component :
+ entry.getValue().getComponents().entrySet()) {
+ SourceMember member = fields.get(component.getKey());
+ if (member != null) {
+ populateMemberJavadoc(
+ javaClass, member, component.getValue(), sourceModel, mojoAnnotatedClasses, linkGenerator);
}
}
}
}
- /**
- * Returns the XHTML description from the given element.
- * This may refer to either goal, parameter or component.
- * @param element the element for which to generate the description
- * @param context the context with which to call the converter
- * @return the generated description
- */
- String getDescriptionFromElement(JavaAnnotatedElement element, JavaClassConverterContext context) {
+ private void populateMemberJavadoc(
+ TypeDeclaration> mojoClass,
+ SourceMember member,
+ Object annotation,
+ JavaSourceModel sourceModel,
+ Map mojoAnnotatedClasses,
+ JavadocLinkGenerator linkGenerator) {
+ JavaClassConverterContext context = new JavaClassConverterContext(
+ mojoClass,
+ member.declaringClass,
+ member.element,
+ sourceModel,
+ mojoAnnotatedClasses,
+ linkGenerator,
+ lineNumber(member.element));
+ String description = getDescriptionFromElement(member.javadocElement, context);
+ Optional deprecated = getTag(member.javadocElement, "deprecated");
+ Optional since = getTag(member.javadocElement, "since");
+ if (annotation instanceof ParameterAnnotationContent) {
+ ParameterAnnotationContent parameter = (ParameterAnnotationContent) annotation;
+ parameter.setDescription(description);
+ deprecated.ifPresent(tag -> parameter.setDeprecated(getRawValueFromTaglet(tag, context)));
+ since.ifPresent(tag -> parameter.setSince(getRawValueFromTaglet(tag, context)));
+ } else {
+ ComponentAnnotationContent component = (ComponentAnnotationContent) annotation;
+ component.setDescription(description);
+ deprecated.ifPresent(tag -> component.setDeprecated(getRawValueFromTaglet(tag, context)));
+ since.ifPresent(tag -> component.setSince(getRawValueFromTaglet(tag, context)));
+ }
+ }
- String comment = element.getComment();
- if (comment == null) {
+ String getDescriptionFromElement(NodeWithJavadoc> element, JavaClassConverterContext context) {
+ Optional javadoc = element.getJavadoc();
+ if (!javadoc.isPresent()) {
return null;
}
- StringBuilder description = new StringBuilder(javadocInlineTagsToHtmlConverter.convert(comment, context));
- for (DocletTag docletTag : element.getTags()) {
- // also consider see block tags
- if ("see".equals(docletTag.getName())) {
- description.append(javadocBlockTagsToHtmlConverter.convert(docletTag, context));
- }
- }
+ StringBuilder description = new StringBuilder(javadocInlineTagsToHtmlConverter.convert(
+ javadoc.get().getDescription().toText(), context));
+ javadoc.get().getBlockTags().stream()
+ .filter(tag -> "see".equals(tag.getTagName()))
+ .forEach(tag -> description.append(javadocBlockTagsToHtmlConverter.convert(
+ tag.getTagName(), tag.getContent().toText(), context)));
return description.toString();
}
- String getRawValueFromTaglet(DocletTag docletTag, ConverterContext context) {
- // just resolve inline tags and convert to XHTML
- return javadocInlineTagsToHtmlConverter.convert(docletTag.getValue(), context);
+ String getRawValueFromTaglet(JavadocBlockTag tag, ConverterContext context) {
+ return javadocInlineTagsToHtmlConverter.convert(tag.getContent().toText(), context);
}
- /**
- * @param javaClass not null
- * @param tagName not null
- * @return docletTag instance
- */
- private DocletTag findInClassHierarchy(JavaClass javaClass, String tagName) {
- try {
- DocletTag tag = javaClass.getTagByName(tagName);
-
- if (tag == null) {
- JavaClass superClass = javaClass.getSuperJavaClass();
-
- if (superClass != null) {
- tag = findInClassHierarchy(superClass, tagName);
- }
- }
-
+ private Optional findInClassHierarchy(
+ JavaSourceModel sourceModel, TypeDeclaration> javaClass, String tagName) {
+ Optional tag = getTag(javaClass, tagName);
+ if (tag.isPresent()) {
return tag;
- } catch (NoClassDefFoundError e) {
- if (e.getMessage().replace('/', '.').contains(MojoAnnotationsScanner.V4_API_PLUGIN_PACKAGE)) {
- return null;
- }
- String str;
- try {
- str = javaClass.getFullyQualifiedName();
- } catch (Throwable t) {
- str = javaClass.getValue();
- }
- LOGGER.warn("Failed extracting tag '" + tagName + "' from class " + str);
- throw (NoClassDefFoundError) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
+ return getSuperSourceClass(sourceModel, javaClass)
+ .flatMap(parent -> findInClassHierarchy(sourceModel, parent, tagName));
}
- /**
- * extract fields that are either parameters or components.
- *
- * @param javaClass not null
- * @return map with Mojo parameters names as keys
- */
- private Map extractFieldsAnnotations(
- JavaClass javaClass, Map javaClassesMap) {
- try {
- Map rawParams = new TreeMap<>();
-
- // we have to add the parent fields first, so that they will be overwritten by the local fields if
- // that actually happens...
- JavaClass superClass = javaClass.getSuperJavaClass();
-
- if (superClass != null) {
- if (!superClass.getFields().isEmpty()) {
- rawParams = extractFieldsAnnotations(superClass, javaClassesMap);
- }
- // maybe sources comes from scan of sources artifact
- superClass = javaClassesMap.get(superClass.getFullyQualifiedName());
- if (superClass != null && !superClass.getFields().isEmpty()) {
- rawParams = extractFieldsAnnotations(superClass, javaClassesMap);
- }
- } else {
-
- rawParams = new TreeMap<>();
- }
-
- for (JavaField field : javaClass.getFields()) {
- rawParams.put(field.getName(), field);
- }
+ private static Optional getTag(NodeWithJavadoc> element, String tagName) {
+ return element.getJavadoc()
+ .flatMap(javadoc -> javadoc.getBlockTags().stream()
+ .filter(tag -> tagName.equals(tag.getTagName()))
+ .findFirst());
+ }
- return rawParams;
- } catch (NoClassDefFoundError e) {
- LOGGER.warn("Failed extracting parameters from " + javaClass);
- throw e;
+ private Map extractFields(JavaSourceModel sourceModel, TypeDeclaration> javaClass) {
+ Map result = getSuperSourceClass(sourceModel, javaClass)
+ .map(parent -> extractFields(sourceModel, parent))
+ .orElseGet(TreeMap::new);
+ for (FieldDeclaration field : javaClass.getFields()) {
+ field.getVariables()
+ .forEach(variable ->
+ result.put(variable.getNameAsString(), new SourceMember(javaClass, field, field)));
}
+ return result;
}
- /**
- * extract methods that are parameters.
- *
- * @param javaClass not null
- * @return map with Mojo parameters names as keys
- */
- private Map extractMethodsAnnotations(
- JavaClass javaClass, Map javaClassesMap) {
- try {
- Map rawParams = new TreeMap<>();
-
- // we have to add the parent methods first, so that they will be overwritten by the local methods if
- // that actually happens...
- JavaClass superClass = javaClass.getSuperJavaClass();
-
- if (superClass != null) {
- if (!superClass.getMethods().isEmpty()) {
- rawParams = extractMethodsAnnotations(superClass, javaClassesMap);
- }
- // maybe sources comes from scan of sources artifact
- superClass = javaClassesMap.get(superClass.getFullyQualifiedName());
- if (superClass != null && !superClass.getMethods().isEmpty()) {
- rawParams = extractMethodsAnnotations(superClass, javaClassesMap);
- }
- } else {
-
- rawParams = new TreeMap<>();
- }
-
- for (JavaMethod method : javaClass.getMethods()) {
- if (isPublicSetterMethod(method)) {
- rawParams.put(
- StringUtils.lowercaseFirstLetter(method.getName().substring(3)), method);
- }
- }
-
- return rawParams;
- } catch (NoClassDefFoundError e) {
- if (e.getMessage().replace('/', '.').contains(MojoAnnotationsScanner.V4_API_PLUGIN_PACKAGE)) {
- return new TreeMap<>();
- }
- String str;
- try {
- str = javaClass.getFullyQualifiedName();
- } catch (Throwable t) {
- str = javaClass.getValue();
+ private Map extractMethods(JavaSourceModel sourceModel, TypeDeclaration> javaClass) {
+ Map result = getSuperSourceClass(sourceModel, javaClass)
+ .map(parent -> extractMethods(sourceModel, parent))
+ .orElseGet(TreeMap::new);
+ for (MethodDeclaration method : javaClass.getMethods()) {
+ if (isPublicSetterMethod(method)) {
+ result.put(
+ StringUtils.lowercaseFirstLetter(
+ method.getNameAsString().substring(3)),
+ new SourceMember(javaClass, method, method));
}
- LOGGER.warn("Failed extracting methods from " + str);
- throw (NoClassDefFoundError) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
+ return result;
}
- private boolean isPublicSetterMethod(JavaMethod method) {
+ private static boolean isPublicSetterMethod(MethodDeclaration method) {
return method.isPublic()
&& !method.isStatic()
- && method.getName().length() > 3
- && (method.getName().startsWith("add") || method.getName().startsWith("set"))
- && "void".equals(method.getReturnType().getValue())
+ && method.getNameAsString().length() > 3
+ && (method.getNameAsString().startsWith("add")
+ || method.getNameAsString().startsWith("set"))
+ && method.getType().isVoidType()
&& method.getParameters().size() == 1;
}
- protected Map discoverClasses(JavaProjectBuilder builder) {
- Collection javaClasses = builder.getClasses();
-
- if (javaClasses == null || javaClasses.size() < 1) {
- return Collections.emptyMap();
+ private Optional> getSuperSourceClass(
+ JavaSourceModel sourceModel, TypeDeclaration> javaClass) {
+ if (!(javaClass instanceof ClassOrInterfaceDeclaration)
+ || ((ClassOrInterfaceDeclaration) javaClass).isInterface()) {
+ return Optional.empty();
}
-
- Map javaClassMap = new HashMap<>(javaClasses.size());
-
- for (JavaClass javaClass : javaClasses) {
- javaClassMap.put(javaClass.getFullyQualifiedName(), javaClass);
+ Optional superClass = ((ClassOrInterfaceDeclaration) javaClass)
+ .getExtendedTypes().stream().findFirst();
+ if (!superClass.isPresent()) {
+ return Optional.empty();
+ }
+ try {
+ return superClass
+ .get()
+ .resolve()
+ .asReferenceType()
+ .getTypeDeclaration()
+ .flatMap(sourceModel::getType);
+ } catch (UnsolvedSymbolException e) {
+ // Sources artifacts need not contain the sources of every superclass. In that case there is no
+ // source Javadoc to inherit, just as when no sources artifact is available at all.
+ LOGGER.debug("Could not resolve source superclass {}", superClass.get(), e);
+ return Optional.empty();
}
+ }
- return javaClassMap;
+ protected Map> discoverClasses(JavaSourceModel sourceModel) {
+ Map> result = new HashMap<>();
+ for (TypeDeclaration> type : sourceModel.getTypes()) {
+ type.getFullyQualifiedName().ifPresent(name -> result.put(name, type));
+ }
+ return result;
}
- protected void extendJavaProjectBuilderWithSourcesJar(
- JavaProjectBuilder builder, Artifact artifact, PluginToolsRequest request, String classifier)
- throws ExtractionException {
+ protected void extendJavaSourceModelWithSourcesJar(
+ JavaSourceModel sourceModel, Artifact artifact, PluginToolsRequest request, String classifier)
+ throws ExtractionException, IOException {
try {
org.eclipse.aether.artifact.Artifact sourcesArtifact = new DefaultArtifact(
artifact.getGroupId(),
@@ -621,17 +536,17 @@ protected void extendJavaProjectBuilderWithSourcesJar(
unArchiver.setDestDirectory(extractDirectory);
unArchiver.extract();
- extendJavaProjectBuilder(builder, Arrays.asList(extractDirectory), request.getDependencies());
+ extendJavaSourceModel(sourceModel, Arrays.asList(extractDirectory), request.getDependencies());
} else if (sourcesArtifact.getFile().isDirectory()) {
- extendJavaProjectBuilder(builder, Arrays.asList(sourcesArtifact.getFile()), request.getDependencies());
+ extendJavaSourceModel(sourceModel, Arrays.asList(sourcesArtifact.getFile()), request.getDependencies());
}
} catch (ArchiverException | NoSuchArchiverException e) {
throw new ExtractionException(e.getMessage(), e);
}
}
- private void extendJavaProjectBuilder(
- PluginToolsRequest request, JavaProjectBuilder builder, final MavenProject project) {
+ private void extendJavaSourceModel(
+ PluginToolsRequest request, JavaSourceModel sourceModel, final MavenProject project) throws IOException {
List sources = new ArrayList<>();
for (String source : project.getCompileSourceRoots()) {
@@ -651,25 +566,32 @@ private void extendJavaProjectBuilder(
sources.add(generatedPlugin);
}
- extendJavaProjectBuilder(builder, sources, project.getArtifacts());
+ extendJavaSourceModel(sourceModel, sources, project.getArtifacts());
}
- private void extendJavaProjectBuilder(
- JavaProjectBuilder builder, List sourceDirectories, Set artifacts) {
-
- // Build isolated Classloader with only the artifacts of the project (none of this plugin)
- List urls = new ArrayList<>(artifacts.size());
+ private void extendJavaSourceModel(
+ JavaSourceModel sourceModel, List sourceDirectories, Set artifacts) throws IOException {
for (Artifact artifact : artifacts) {
- try {
- urls.add(artifact.getFile().toURI().toURL());
- } catch (MalformedURLException e) {
- // noop
- }
+ sourceModel.addClassPathEntry(artifact.getFile());
}
- builder.addClassLoader(new URLClassLoader(urls.toArray(new URL[0]), ClassLoader.getSystemClassLoader()));
-
for (File source : sourceDirectories) {
- builder.addSourceTree(source);
+ sourceModel.addSourceDirectory(source);
+ }
+ }
+
+ private static int lineNumber(Node node) {
+ return node.getBegin().map(position -> position.line).orElse(0);
+ }
+
+ private static final class SourceMember {
+ private final TypeDeclaration> declaringClass;
+ private final Node element;
+ private final NodeWithJavadoc> javadocElement;
+
+ private SourceMember(TypeDeclaration> declaringClass, Node element, NodeWithJavadoc> javadocElement) {
+ this.declaringClass = declaringClass;
+ this.element = element;
+ this.javadocElement = javadocElement;
}
}
diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaSourceModel.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaSourceModel.java
new file mode 100644
index 000000000..ac1e8a49a
--- /dev/null
+++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaSourceModel.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.maven.tools.plugin.extractor.annotations;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.github.javaparser.ParseResult;
+import com.github.javaparser.ParserConfiguration;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.ast.modules.ModuleExportsDirective;
+import com.github.javaparser.resolution.TypeSolver;
+import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
+import com.github.javaparser.resolution.model.SymbolReference;
+import com.github.javaparser.symbolsolver.JavaSymbolSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.ClassLoaderTypeSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
+import com.github.javaparser.utils.SourceRoot;
+
+/** Source declarations and type resolution used while extracting Javadocs. */
+public final class JavaSourceModel implements Closeable {
+
+ private final Charset encoding;
+ private final Set sourceDirectories = new LinkedHashSet<>();
+ private final Set classPathEntries = new LinkedHashSet<>();
+ private final Map> types = new LinkedHashMap<>();
+ private final Map modulesByExportedPackage = new LinkedHashMap<>();
+ private final Set packages = new LinkedHashSet<>();
+ private final Set internalPackages = new LinkedHashSet<>();
+
+ private ParserConfiguration parserConfiguration;
+ private CombinedTypeSolver typeSolver;
+ private URLClassLoader classPathLoader;
+ private boolean parsed;
+
+ public JavaSourceModel(Charset encoding) {
+ this.encoding = encoding;
+ }
+
+ public void addSourceDirectory(File directory) throws IOException {
+ if (directory != null && directory.isDirectory()) {
+ sourceDirectories.add(directory.toPath().toRealPath());
+ }
+ }
+
+ public void addClassPathEntry(File entry) throws IOException {
+ if (entry != null && entry.exists()) {
+ classPathEntries.add(entry.toPath().toRealPath());
+ }
+ }
+
+ public void parse() throws IOException {
+ if (parsed) {
+ return;
+ }
+ parsed = true;
+
+ parserConfiguration = new ParserConfiguration()
+ .setCharacterEncoding(encoding)
+ .setLanguageLevel(ParserConfiguration.LanguageLevel.BLEEDING_EDGE);
+ typeSolver = new CombinedTypeSolver();
+ for (Path sourceDirectory : sourceDirectories) {
+ typeSolver.add(new JavaParserTypeSolver(sourceDirectory, parserConfiguration));
+ }
+
+ List classPathUrls = new ArrayList<>();
+ for (Path classPathEntry : classPathEntries) {
+ if (Files.isDirectory(classPathEntry)) {
+ classPathUrls.add(classPathEntry.toUri().toURL());
+ indexClassDirectory(classPathEntry);
+ } else if (classPathEntry.getFileName().toString().endsWith(".jar")) {
+ classPathUrls.add(classPathEntry.toUri().toURL());
+ JarTypeSolver jarTypeSolver = new JarTypeSolver(classPathEntry);
+ typeSolver.add(jarTypeSolver);
+ jarTypeSolver.getKnownClasses().stream()
+ .map(JavaSourceModel::packageName)
+ .filter(name -> !name.isEmpty())
+ .forEach(packages::add);
+ }
+ }
+ if (!classPathUrls.isEmpty()) {
+ classPathLoader = new URLClassLoader(classPathUrls.toArray(new URL[0]), ClassLoader.getSystemClassLoader());
+ typeSolver.add(new ClassLoaderTypeSolver(classPathLoader));
+ }
+ typeSolver.add(new ReflectionTypeSolver(ReflectionTypeSolver.JCL_ONLY));
+ parserConfiguration.setSymbolResolver(new JavaSymbolSolver(typeSolver));
+
+ for (Path sourceDirectory : sourceDirectories) {
+ SourceRoot sourceRoot = new SourceRoot(sourceDirectory, parserConfiguration);
+ for (ParseResult result : sourceRoot.tryToParse()) {
+ if (!result.isSuccessful()) {
+ String path = result.getSourcePath().map(Path::toString).orElse(sourceDirectory.toString());
+ String problems = result.getProblems().stream()
+ .map(Object::toString)
+ .collect(Collectors.joining(System.lineSeparator()));
+ throw new IOException("Unable to parse " + path + System.lineSeparator() + problems);
+ }
+ CompilationUnit unit = result.getResult()
+ .orElseThrow(() ->
+ new IOException("Parser returned no compilation unit for " + result.getSourcePath()));
+ index(unit);
+ }
+ }
+ }
+
+ private void indexClassDirectory(Path directory) throws IOException {
+ try (Stream entries = Files.walk(directory)) {
+ entries.filter(Files::isRegularFile)
+ .map(directory::relativize)
+ .filter(path -> path.getFileName().toString().endsWith(".class"))
+ .map(Path::getParent)
+ .filter(path -> path != null)
+ .map(Path::toString)
+ .map(name -> name.replace(File.separatorChar, '.'))
+ .filter(name -> !name.isEmpty())
+ .forEach(packages::add);
+ }
+ }
+
+ private void index(CompilationUnit unit) {
+ unit.getPackageDeclaration()
+ .map(declaration -> declaration.getName().asString())
+ .ifPresent(name -> {
+ packages.add(name);
+ internalPackages.add(name);
+ });
+ for (TypeDeclaration> type : unit.findAll(TypeDeclaration.class)) {
+ type.getFullyQualifiedName().ifPresent(name -> types.putIfAbsent(name, type));
+ }
+ unit.getModule()
+ .ifPresent(module -> module.getDirectives().stream()
+ .filter(ModuleExportsDirective.class::isInstance)
+ .map(ModuleExportsDirective.class::cast)
+ .forEach(exports -> modulesByExportedPackage.putIfAbsent(
+ exports.getName().asString(), module.getName().asString())));
+ }
+
+ public Collection> getTypes() {
+ return Collections.unmodifiableCollection(types.values());
+ }
+
+ public Optional> getType(String fullyQualifiedName) {
+ return Optional.ofNullable(types.get(fullyQualifiedName));
+ }
+
+ public Optional> getType(ResolvedReferenceTypeDeclaration declaration) {
+ return getType(declaration.getQualifiedName());
+ }
+
+ public Optional getModuleName(String packageName) {
+ return Optional.ofNullable(modulesByExportedPackage.get(packageName));
+ }
+
+ public boolean hasPackage(String packageName) {
+ return packages.contains(packageName) || Package.getPackage(packageName) != null;
+ }
+
+ public boolean isInternal(ResolvedReferenceTypeDeclaration declaration) {
+ return types.containsKey(declaration.getQualifiedName());
+ }
+
+ public boolean isInternalPackage(String packageName) {
+ return internalPackages.contains(packageName);
+ }
+
+ public Optional resolveType(String fullyQualifiedName) {
+ ensureParsed();
+ SymbolReference reference = typeSolver.tryToSolveType(fullyQualifiedName);
+ return reference.isSolved() ? Optional.of(reference.getCorrespondingDeclaration()) : Optional.empty();
+ }
+
+ public TypeSolver getTypeSolver() {
+ ensureParsed();
+ return typeSolver;
+ }
+
+ public String getLocation(Node node, int fallbackLine) {
+ int line = fallbackLine > 0
+ ? fallbackLine
+ : node.getBegin().map(position -> position.line).orElse(0);
+ return node.findCompilationUnit()
+ .flatMap(CompilationUnit::getStorage)
+ .map(storage -> java.nio.file.Paths.get("")
+ .toAbsolutePath()
+ .toUri()
+ .relativize(storage.getPath().toUri())
+ .toString()
+ + ":" + line)
+ .orElse("unknown:" + line);
+ }
+
+ private void ensureParsed() {
+ if (!parsed) {
+ throw new IllegalStateException("Java source model has not been parsed");
+ }
+ }
+
+ private static String packageName(String className) {
+ int separator = className.lastIndexOf('.');
+ return separator > 0 ? className.substring(0, separator) : "";
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (classPathLoader != null) {
+ classPathLoader.close();
+ }
+ }
+}
diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavaClassConverterContext.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavaClassConverterContext.java
index b039d5b49..bc6bf04f5 100644
--- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavaClassConverterContext.java
+++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavaClassConverterContext.java
@@ -20,8 +20,6 @@
import java.net.URI;
import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -30,121 +28,101 @@
import java.util.Optional;
import java.util.stream.Collectors;
-import com.thoughtworks.qdox.JavaProjectBuilder;
-import com.thoughtworks.qdox.builder.TypeAssembler;
-import com.thoughtworks.qdox.library.ClassNameLibrary;
-import com.thoughtworks.qdox.model.JavaClass;
-import com.thoughtworks.qdox.model.JavaField;
-import com.thoughtworks.qdox.model.JavaModule;
-import com.thoughtworks.qdox.model.JavaPackage;
-import com.thoughtworks.qdox.model.JavaType;
-import com.thoughtworks.qdox.parser.structs.TypeDef;
-import com.thoughtworks.qdox.type.TypeResolver;
+import com.github.javaparser.ast.ImportDeclaration;
+import com.github.javaparser.ast.Node;
+import com.github.javaparser.ast.body.FieldDeclaration;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration;
+import com.github.javaparser.resolution.declarations.ResolvedFieldDeclaration;
+import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
+import com.github.javaparser.resolution.declarations.ResolvedMethodLikeDeclaration;
+import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
+import com.github.javaparser.resolution.types.ResolvedReferenceType;
+import org.apache.maven.tools.plugin.extractor.annotations.JavaSourceModel;
import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotatedClass;
import org.apache.maven.tools.plugin.javadoc.FullyQualifiedJavadocReference;
import org.apache.maven.tools.plugin.javadoc.FullyQualifiedJavadocReference.MemberType;
import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator;
import org.apache.maven.tools.plugin.javadoc.JavadocReference;
-/** {@link ConverterContext} based on QDox's {@link JavaClass} and {@link JavaProjectBuilder}. */
+/** {@link ConverterContext} backed by JavaParser source declarations and symbol resolution. */
public class JavaClassConverterContext implements ConverterContext {
- final JavaClass mojoClass; // this is the mojo's class
+ /** The Mojo class whose documentation is being generated. */
+ private final TypeDeclaration> mojoClass;
- final JavaClass declaringClass; // this may be a super class of the mojo's class
+ /** The class declaring the converted Javadoc, possibly a superclass of the Mojo class. */
+ private final TypeDeclaration> declaringClass;
- final JavaProjectBuilder javaProjectBuilder;
+ private final Node locationNode;
+ private final JavaSourceModel sourceModel;
+ private final Map mojoAnnotatedClasses;
- final Map mojoAnnotatedClasses;
+ /** The link generator, or {@code null} when no Javadoc site is configured. */
+ private final JavadocLinkGenerator linkGenerator;
- final JavadocLinkGenerator linkGenerator; // may be null in case nothing was configured
-
- final int lineNumber;
-
- final Optional javaModule;
-
- final Map attributes;
+ private final int lineNumber;
+ private final Map attributes = new HashMap<>();
public JavaClassConverterContext(
- JavaClass mojoClass,
- JavaProjectBuilder javaProjectBuilder,
+ TypeDeclaration> mojoClass,
+ JavaSourceModel sourceModel,
Map mojoAnnotatedClasses,
JavadocLinkGenerator linkGenerator,
int lineNumber) {
- this(mojoClass, mojoClass, javaProjectBuilder, mojoAnnotatedClasses, linkGenerator, lineNumber);
+ this(mojoClass, mojoClass, mojoClass, sourceModel, mojoAnnotatedClasses, linkGenerator, lineNumber);
}
public JavaClassConverterContext(
- JavaClass mojoClass,
- JavaClass declaringClass,
- JavaProjectBuilder javaProjectBuilder,
+ TypeDeclaration> mojoClass,
+ TypeDeclaration> declaringClass,
+ Node locationNode,
+ JavaSourceModel sourceModel,
Map mojoAnnotatedClasses,
JavadocLinkGenerator linkGenerator,
int lineNumber) {
this.mojoClass = mojoClass;
this.declaringClass = declaringClass;
- this.javaProjectBuilder = javaProjectBuilder;
+ this.locationNode = locationNode;
+ this.sourceModel = sourceModel;
this.mojoAnnotatedClasses = mojoAnnotatedClasses;
this.linkGenerator = linkGenerator;
this.lineNumber = lineNumber;
- this.attributes = new HashMap<>();
-
- javaModule = mojoClass.getJavaClassLibrary().getJavaModules().stream()
- .filter(m -> m.getDescriptor().getExports().stream()
- .anyMatch(e -> e.getSource().getName().equals(getPackageName())))
- .findFirst();
}
@Override
public Optional getModuleName() {
- // https://github.com/paul-hammant/qdox/issues/113, module name is not exposed
- return javaModule.map(JavaModule::getName);
+ return sourceModel.getModuleName(getPackageName());
}
@Override
public String getPackageName() {
- return mojoClass.getPackageName();
+ return resolve(mojoClass).getPackageName();
}
@Override
public String getLocation() {
- try {
- URL url = declaringClass.getSource().getURL();
- if (url == null) // url is not always available, just emit FQCN in that case
- {
- return declaringClass.getPackageName() + declaringClass.getSimpleName() + ":" + lineNumber;
- }
- return Paths.get("").toUri().relativize(url.toURI()) + ":" + lineNumber;
- } catch (URISyntaxException e) {
- return declaringClass.getSource().getURL() + ":" + lineNumber;
- }
+ return sourceModel.getLocation(locationNode, lineNumber);
}
- /**
- * @param reference
- * @return true in case either the current context class or any of its super classes are referenced
- */
+ /** Returns whether {@code reference} identifies the Mojo class or one of its ancestors. */
@Override
public boolean isReferencedBy(FullyQualifiedJavadocReference reference) {
- JavaClass javaClassInHierarchy = this.mojoClass;
- while (javaClassInHierarchy != null) {
- if (isClassReferencedByReference(javaClassInHierarchy, reference)) {
- return true;
- }
- // check implemented interfaces
- for (JavaClass implementedInterfaces : javaClassInHierarchy.getInterfaces()) {
- if (isClassReferencedByReference(implementedInterfaces, reference)) {
- return true;
- }
- }
- javaClassInHierarchy = javaClassInHierarchy.getSuperJavaClass();
+ ResolvedReferenceTypeDeclaration declaration = resolve(mojoClass);
+ if (isClassReferencedByReference(declaration, reference)) {
+ return true;
}
- return false;
+ return declaration.getAllAncestors().stream()
+ .map(ResolvedReferenceType::getTypeDeclaration)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .anyMatch(type -> isClassReferencedByReference(type, reference));
}
- private static boolean isClassReferencedByReference(JavaClass javaClass, FullyQualifiedJavadocReference reference) {
- return javaClass.getPackageName().equals(reference.getPackageName().orElse(""))
- && javaClass.getSimpleName().equals(reference.getClassName().orElse(""));
+ private static boolean isClassReferencedByReference(
+ ResolvedReferenceTypeDeclaration declaration, FullyQualifiedJavadocReference reference) {
+ return declaration.getPackageName().equals(reference.getPackageName().orElse(""))
+ && declaration.getClassName().equals(reference.getClassName().orElse(""));
}
@Override
@@ -157,8 +135,8 @@ public URI getUrl(FullyQualifiedJavadocReference reference) {
try {
if (isReferencedBy(reference)
&& MemberType.FIELD == reference.getMemberType().orElse(null)) {
- // link to current goal's parameters
- return new URI(null, null, reference.getMember().orElse(null)); // just an anchor if same context
+ // Fields in the current Mojo link to parameter anchors on the same page.
+ return new URI(null, null, reference.getMember().orElse(null));
}
Optional fqClassName = reference.getFullyQualifiedClassName();
if (fqClassName.isPresent()) {
@@ -167,7 +145,7 @@ public URI getUrl(FullyQualifiedJavadocReference reference) {
&& mojoAnnotatedClass.getMojo() != null
&& (!reference.getLabel().isPresent()
|| MemberType.FIELD == reference.getMemberType().orElse(null))) {
- // link to other mojo (only for fields = parameters or without member)
+ // Fields and whole-class references to another Mojo link to that Mojo's page.
return new URI(
null,
"./" + mojoAnnotatedClass.getMojo().name() + "-mojo.html",
@@ -175,7 +153,7 @@ public URI getUrl(FullyQualifiedJavadocReference reference) {
}
}
} catch (URISyntaxException e) {
- throw new IllegalStateException("Error constructing a valid URL", e); // should not happen
+ throw new IllegalStateException("Error constructing a valid URL", e);
}
if (linkGenerator == null) {
throw new IllegalStateException("No Javadoc Sites given to create URLs to");
@@ -185,296 +163,352 @@ public URI getUrl(FullyQualifiedJavadocReference reference) {
@Override
public FullyQualifiedJavadocReference resolveReference(JavadocReference reference) {
- Optional resolvedName;
- // is it already fully qualified?
+ Optional resolved;
+ // First try the reference exactly as written, which handles fully qualified names.
if (reference.getPackageNameClassName().isPresent()) {
- resolvedName = resolveMember(
- reference.getPackageNameClassName().get(), reference.getMember(), reference.getLabel());
- if (resolvedName.isPresent()) {
- return resolvedName.get();
+ String name = reference.getPackageNameClassName().get();
+ resolved = resolveNamedReference(name, reference.getMember(), reference.getLabel());
+ if (resolved.isPresent()) {
+ return resolved.get();
}
}
- // is it a member only?
+
+ // Search member-only references in the current class or interface first, then its ancestors.
if (reference.getMember().isPresent()
&& !reference.getPackageNameClassName().isPresent()) {
- // search order for not fully qualified names:
- // 1. The current class or interface (only for members)
- resolvedName = resolveMember(declaringClass, reference.getMember(), reference.getLabel());
- if (resolvedName.isPresent()) {
- return resolvedName.get();
- }
- // 2. Any enclosing classes and interfaces searching the closest first (only members)
- for (JavaClass nestedClass : declaringClass.getNestedClasses()) {
- resolvedName = resolveMember(nestedClass, reference.getMember(), reference.getLabel());
- if (resolvedName.isPresent()) {
- return resolvedName.get();
- }
+ resolved = resolveMember(resolve(declaringClass), reference.getMember(), reference.getLabel(), true);
+ if (resolved.isPresent()) {
+ return resolved.get();
}
- // 3. Any superclasses and superinterfaces, searching the closest first. (only members)
- JavaClass superClass = declaringClass.getSuperJavaClass();
- while (superClass != null) {
- resolvedName = resolveMember(superClass, reference.getMember(), reference.getLabel());
- if (resolvedName.isPresent()) {
- return resolvedName.get();
- }
- superClass = superClass.getSuperJavaClass();
- }
- } else {
- String packageNameClassName = reference.getPackageNameClassName().get();
- // 4. The current package
- resolvedName = resolveMember(
- declaringClass.getPackageName() + "." + packageNameClassName,
- reference.getMember(),
- reference.getLabel());
- if (resolvedName.isPresent()) {
- return resolvedName.get();
- }
- // 5. Any imported packages, classes, and interfaces, searching in the order of the import statement.
- List importNames = new ArrayList<>();
- importNames.add("java.lang.*"); // default import
- importNames.addAll(declaringClass.getSource().getImports());
- for (String importName : importNames) {
- if (importName.endsWith(".*")) {
- resolvedName = resolveMember(
- importName.replace("*", packageNameClassName), reference.getMember(), reference.getLabel());
- if (resolvedName.isPresent()) {
- return resolvedName.get();
- }
- } else {
- if (importName.endsWith(packageNameClassName)) {
- resolvedName = resolveMember(importName, reference.getMember(), reference.getLabel());
- if (resolvedName.isPresent()) {
- return resolvedName.get();
- }
- } else {
- // ends with prefix of reference (nested class name)
- int firstDotIndex = packageNameClassName.indexOf(".");
- if (firstDotIndex > 0
- && importName.endsWith(packageNameClassName.substring(0, firstDotIndex))) {
- resolvedName = resolveMember(
- importName,
- packageNameClassName.substring(firstDotIndex + 1),
- reference.getMember(),
- reference.getLabel());
- if (resolvedName.isPresent()) {
- return resolvedName.get();
- }
- }
- }
+ } else if (reference.getPackageNameClassName().isPresent()) {
+ // Resolve simple and nested type names using Javadoc's lookup order.
+ String name = reference.getPackageNameClassName().get();
+ for (String candidate : classNameCandidates(name)) {
+ resolved = resolveNamedReference(candidate, reference.getMember(), reference.getLabel());
+ if (resolved.isPresent()) {
+ return resolved.get();
}
}
}
throw new IllegalArgumentException("Could not resolve javadoc reference " + reference);
}
- @Override
- public String getStaticFieldValue(FullyQualifiedJavadocReference reference) {
- String fqcn = reference
- .getFullyQualifiedClassName()
- .orElseThrow(() ->
- new IllegalArgumentException("Given reference does not specify a fully qualified class name!"));
- String fieldName = reference
- .getMember()
- .orElseThrow(() -> new IllegalArgumentException("Given reference does not specify a member!"));
- JavaClass javaClass = javaProjectBuilder.getClassByName(fqcn);
- JavaField javaField = javaClass.getFieldByName(fieldName);
- if (javaField == null) {
- throw new IllegalArgumentException("Could not find field with name " + fieldName + " in class " + fqcn);
+ private Optional resolveNamedReference(
+ String name, Optional member, Optional label) {
+ Optional type = sourceModel.resolveType(name);
+ if (type.isPresent()) {
+ return resolveMember(type.get(), member, label, true);
}
- if (!javaField.isStatic()) {
- throw new IllegalArgumentException("Field with name " + fieldName + " in class " + fqcn + " is not static");
+ // Package references cannot contain members.
+ if (!member.isPresent() && sourceModel.hasPackage(name)) {
+ return Optional.of(new FullyQualifiedJavadocReference(name, label, !sourceModel.isInternalPackage(name)));
}
- return javaField.getInitializationExpression();
+ return Optional.empty();
}
- @Override
- public URI getInternalJavadocSiteBaseUrl() {
- return linkGenerator.getInternalJavadocSiteBaseUrl();
- }
-
- private Optional resolveMember(
- String fullyQualifiedPackageNameClassName, Optional member, Optional label) {
- return resolveMember(fullyQualifiedPackageNameClassName, "", member, label);
+ private List classNameCandidates(String name) {
+ List candidates = new ArrayList<>();
+ ResolvedReferenceTypeDeclaration declaration = resolve(declaringClass);
+ // Search order: current package, implicit java.lang import, then explicit imports in declaration order.
+ candidates.add(declaration.getPackageName() + "." + name);
+ candidates.add("java.lang." + name);
+ List imports = declaringClass
+ .findCompilationUnit()
+ .>map(unit -> new ArrayList<>(unit.getImports()))
+ .orElseGet(Collections::emptyList);
+ for (ImportDeclaration importDeclaration : imports) {
+ if (importDeclaration.isStatic()) {
+ continue;
+ }
+ String importName = importDeclaration.getNameAsString();
+ if (importDeclaration.isAsterisk()) {
+ candidates.add(importName + "." + name);
+ } else if (name.equals(simpleName(importName))) {
+ candidates.add(importName);
+ } else if (name.startsWith(simpleName(importName) + ".")) {
+ // An imported outer type may prefix a nested-type reference.
+ candidates.add(
+ importName + name.substring(simpleName(importName).length()));
+ }
+ }
+ return candidates;
}
private Optional resolveMember(
- String fullyQualifiedPackageNameClassName,
- String nestedClassName,
+ ResolvedReferenceTypeDeclaration type,
Optional member,
- Optional label) {
- JavaClass javaClass = javaProjectBuilder.getClassByName(fullyQualifiedPackageNameClassName);
- if (!isClassFound(javaClass)) {
- JavaPackage javaPackage = javaProjectBuilder.getPackageByName(fullyQualifiedPackageNameClassName);
- if (javaPackage == null || !nestedClassName.isEmpty()) {
- // is it a nested class?
- int lastIndexOfDot = fullyQualifiedPackageNameClassName.lastIndexOf('.');
- if (lastIndexOfDot > 0) {
- String newNestedClassName = nestedClassName;
- if (!newNestedClassName.isEmpty()) {
- newNestedClassName += '.';
- }
- newNestedClassName += fullyQualifiedPackageNameClassName.substring(lastIndexOfDot + 1);
- return resolveMember(
- fullyQualifiedPackageNameClassName.substring(0, lastIndexOfDot),
- newNestedClassName,
- member,
- label);
- }
- return Optional.empty();
- } else {
- // reference to java package never has a member
- return Optional.of(
- new FullyQualifiedJavadocReference(javaPackage.getName(), label, isExternal(javaPackage)));
- }
- } else {
- if (!nestedClassName.isEmpty()) {
- javaClass = javaClass.getNestedClassByName(nestedClassName);
- if (javaClass == null) {
- return Optional.empty();
- }
- }
+ Optional label,
+ boolean includeAncestors) {
+ if (!member.isPresent()) {
+ return Optional.of(toReference(type, Optional.empty(), Optional.empty(), label));
+ }
+
+ String memberText = member.get();
+ // Resolve ambiguous member text as a field, method, then constructor.
+ Optional field = findField(type, memberText, includeAncestors);
+ if (field.isPresent()) {
+ return Optional.of(toReference(
+ field.get().declaringType().asReferenceType(), member, Optional.of(MemberType.FIELD), label));
+ }
- return resolveMember(javaClass, member, label);
+ String methodName = methodName(memberText);
+ Optional> parameterTypes = parameterTypes(memberText);
+ Optional method = findMethod(type, methodName, parameterTypes, includeAncestors);
+ if (method.isPresent()) {
+ return Optional.of(toReference(
+ method.get().declaringType(),
+ Optional.of(canonicalMember(method.get())),
+ Optional.of(MemberType.METHOD),
+ label));
+ }
+
+ if (methodName.equals(type.getName())) {
+ Optional constructor = findConstructor(type, parameterTypes);
+ if (constructor.isPresent()) {
+ return Optional.of(toReference(
+ type,
+ Optional.of(canonicalMember(constructor.get())),
+ Optional.of(MemberType.CONSTRUCTOR),
+ label));
+ }
}
+ return Optional.empty();
}
- private boolean isExternal(JavaClass javaClass) {
- return isExternal(javaClass.getPackage());
+ private Optional findField(
+ ResolvedReferenceTypeDeclaration type, String name, boolean includeAncestors) {
+ List fields = includeAncestors ? type.getAllFields() : type.getDeclaredFields();
+ return fields.stream().filter(field -> field.getName().equals(name)).findFirst();
}
- private boolean isExternal(JavaPackage javaPackage) {
- return !javaPackage.getJavaClassLibrary().equals(mojoClass.getJavaClassLibrary());
+ private Optional findMethod(
+ ResolvedReferenceTypeDeclaration type,
+ String name,
+ Optional> parameterTypes,
+ boolean includeAncestors) {
+ List hierarchy = new ArrayList<>();
+ hierarchy.add(type);
+ if (includeAncestors) {
+ type.getAllAncestors().stream()
+ .map(ResolvedReferenceType::getTypeDeclaration)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .forEach(hierarchy::add);
+ }
+ for (ResolvedReferenceTypeDeclaration declaration : hierarchy) {
+ Optional method = declaration.getDeclaredMethods().stream()
+ .filter(candidate -> candidate.getName().equals(name))
+ .filter(candidate -> matches(candidate, parameterTypes))
+ .findFirst();
+ if (method.isPresent()) {
+ return method;
+ }
+ }
+ return Optional.empty();
}
- private Optional resolveMember(
- JavaClass javaClass, Optional member, Optional label) {
- final Optional memberType;
- Optional resolvedMember = member;
- if (member.isPresent()) {
- // member is either field...
- if (javaClass.getFieldByName(member.get()) == null) {
- // ...is method...
- List parameterTypes = getParameterTypes(member.get());
- String methodName = getMethodName(member.get());
- if (javaClass.getMethodBySignature(methodName, parameterTypes) == null) {
- // ...or is constructor
- if ((!methodName.equals(javaClass.getSimpleName()))
- || (javaClass.getConstructor(parameterTypes) == null)) {
- return Optional.empty();
- } else {
- memberType = Optional.of(MemberType.CONSTRUCTOR);
- }
- } else {
- memberType = Optional.of(MemberType.METHOD);
- }
- // reconstruct member with fully qualified names but leaving out the argument names
- StringBuilder memberBuilder = new StringBuilder(methodName);
- memberBuilder.append("(");
- memberBuilder.append(parameterTypes.stream()
- .map(JavaType::getFullyQualifiedName)
- .collect(Collectors.joining(",")));
- memberBuilder.append(")");
- resolvedMember = Optional.of(memberBuilder.toString());
- } else {
- memberType = Optional.of(MemberType.FIELD);
+ private Optional findConstructor(
+ ResolvedReferenceTypeDeclaration type, Optional> parameterTypes) {
+ return type.getConstructors().stream()
+ .filter(candidate -> matches(candidate, parameterTypes))
+ .findFirst();
+ }
+
+ private boolean matches(ResolvedMethodLikeDeclaration declaration, Optional> parameterTypes) {
+ if (!parameterTypes.isPresent()) {
+ return true;
+ }
+ if (declaration.getNumberOfParams() != parameterTypes.get().size()) {
+ return false;
+ }
+ for (int index = 0; index < declaration.getNumberOfParams(); index++) {
+ String actual = declaration.getParam(index).getType().erasure().describe();
+ if (!actual.equals(parameterTypes.get().get(index))) {
+ return false;
}
- } else {
- memberType = Optional.empty();
}
- String className = javaClass
- .getCanonicalName()
- .substring(javaClass.getPackageName().length() + 1);
- return Optional.of(new FullyQualifiedJavadocReference(
- javaClass.getPackageName(),
- Optional.of(className),
- resolvedMember,
- memberType,
- label,
- isExternal(javaClass)));
- }
-
- private static boolean isClassFound(JavaClass javaClass) {
- // this is never null due to using the ClassNameLibrary in the builder
- // but every instance of ClassNameLibrary basically means that the class was not found
- return !(javaClass.getJavaClassLibrary() instanceof ClassNameLibrary);
- }
-
- // https://github.com/paul-hammant/qdox/issues/104
- private List getParameterTypes(String member) {
- List parameterTypes = new ArrayList<>();
- // TypeResolver.byClassName() always resolves types as non existing inner class
- TypeResolver typeResolver = TypeResolver.byClassName(
- declaringClass.getPackageName(),
- declaringClass.getJavaClassLibrary(),
- declaringClass.getSource().getImports());
-
- // method parameters are optionally enclosed by parentheses
- int indexOfOpeningParenthesis = member.indexOf('(');
- int indexOfClosingParenthesis = member.indexOf(')');
- final String signatureArguments;
- if (indexOfOpeningParenthesis >= 0
- && indexOfClosingParenthesis > 0
- && indexOfClosingParenthesis > indexOfOpeningParenthesis) {
- signatureArguments = member.substring(indexOfOpeningParenthesis + 1, indexOfClosingParenthesis);
- } else if (indexOfOpeningParenthesis == -1 && indexOfClosingParenthesis >= 0
- || indexOfOpeningParenthesis >= 0 && indexOfOpeningParenthesis == -1) {
+ return true;
+ }
+
+ private Optional> parameterTypes(String member) {
+ int opening = member.indexOf('(');
+ int closing = member.lastIndexOf(')');
+ if (opening < 0 && closing < 0) {
+ // Without parentheses, match the first overload found, mirroring javadoc.
+ return Optional.empty();
+ }
+ if (opening < 0 || closing < opening) {
throw new IllegalArgumentException("Found opening without closing parentheses or vice versa in " + member);
- } else {
- // If any method or constructor is entered as a name with no parentheses, such as getValue,
- // and if there is no field with the same name, then the javadoc command still creates a
- // link to the method. If this method is overloaded, then the javadoc command links to the
- // first method its search encounters, which is unspecified
- // (Source: https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#JSWOR654)
- return Collections.emptyList();
}
- for (String parameter : signatureArguments.split(",")) {
- // strip off argument name, only type is relevant
- String canonicalParameter = parameter.trim();
- int spaceIndex = canonicalParameter.indexOf(' ');
- final String typeName;
- if (spaceIndex > 0) {
- typeName = canonicalParameter.substring(0, spaceIndex).trim();
- } else {
- typeName = canonicalParameter;
- }
- if (!typeName.isEmpty()) {
- String rawTypeName = getRawTypeName(typeName);
- // already check here for unresolvable types due to https://github.com/paul-hammant/qdox/issues/111
- if (typeResolver.resolveType(rawTypeName) == null) {
- throw new IllegalArgumentException("Found unresolvable method argument type in " + member);
- }
- TypeDef typeDef = new TypeDef(getRawTypeName(typeName));
- int dimensions = getDimensions(typeName);
- JavaType javaType = TypeAssembler.createUnresolved(typeDef, dimensions, typeResolver);
+ String arguments = member.substring(opening + 1, closing).trim();
+ if (arguments.isEmpty()) {
+ return Optional.of(Collections.emptyList());
+ }
+ List result = new ArrayList<>();
+ for (String argument : splitArguments(arguments)) {
+ result.add(resolveParameterType(stripArgumentName(argument.trim())));
+ }
+ return Optional.of(result);
+ }
- parameterTypes.add(javaType);
+ private static List splitArguments(String arguments) {
+ List result = new ArrayList<>();
+ int genericDepth = 0;
+ int start = 0;
+ for (int index = 0; index < arguments.length(); index++) {
+ char ch = arguments.charAt(index);
+ if (ch == '<') {
+ genericDepth++;
+ } else if (ch == '>') {
+ genericDepth--;
+ } else if (ch == ',' && genericDepth == 0) {
+ result.add(arguments.substring(start, index));
+ start = index + 1;
}
}
- return parameterTypes;
+ result.add(arguments.substring(start));
+ return result;
}
- private static int getDimensions(String type) {
- return (int) type.chars().filter(ch -> ch == '[').count();
+ private static String stripArgumentName(String argument) {
+ int genericDepth = 0;
+ for (int index = argument.length() - 1; index >= 0; index--) {
+ char ch = argument.charAt(index);
+ if (ch == '>') {
+ genericDepth++;
+ } else if (ch == '<') {
+ genericDepth--;
+ } else if (Character.isWhitespace(ch) && genericDepth == 0) {
+ return argument.substring(0, index).trim();
+ }
+ }
+ return argument;
}
- private static String getRawTypeName(String typeName) {
- // strip dimensions
- int indexOfOpeningBracket = typeName.indexOf('[');
- if (indexOfOpeningBracket >= 0) {
- return typeName.substring(0, indexOfOpeningBracket);
+ private String resolveParameterType(String typeName) {
+ String normalized = eraseGenerics(typeName.replace("...", "[]"));
+ int dimensions = 0;
+ while (normalized.endsWith("[]")) {
+ dimensions++;
+ normalized = normalized.substring(0, normalized.length() - 2);
+ }
+ String resolved;
+ if (isPrimitive(normalized)) {
+ resolved = normalized;
} else {
- return typeName;
+ List candidates = new ArrayList<>();
+ candidates.add(normalized);
+ candidates.addAll(classNameCandidates(normalized));
+ resolved = candidates.stream()
+ .filter(candidate -> sourceModel.resolveType(candidate).isPresent())
+ .findFirst()
+ .orElseThrow(
+ () -> new IllegalArgumentException("Found unresolvable method argument type " + typeName));
}
+ StringBuilder result = new StringBuilder(resolved);
+ for (int index = 0; index < dimensions; index++) {
+ result.append("[]");
+ }
+ return result.toString();
}
- private static String getMethodName(String member) {
- // name is separated from arguments either by '(' or spans the full member
- int indexOfOpeningParentheses = member.indexOf('(');
- if (indexOfOpeningParentheses == -1) {
- return member;
- } else {
- return member.substring(0, indexOfOpeningParentheses);
+ private static String eraseGenerics(String value) {
+ StringBuilder result = new StringBuilder();
+ int depth = 0;
+ for (int index = 0; index < value.length(); index++) {
+ char ch = value.charAt(index);
+ if (ch == '<') {
+ depth++;
+ } else if (ch == '>') {
+ depth--;
+ } else if (depth == 0) {
+ result.append(ch);
+ }
+ }
+ return result.toString();
+ }
+
+ private static boolean isPrimitive(String name) {
+ return "boolean".equals(name)
+ || "byte".equals(name)
+ || "char".equals(name)
+ || "double".equals(name)
+ || "float".equals(name)
+ || "int".equals(name)
+ || "long".equals(name)
+ || "short".equals(name);
+ }
+
+ private FullyQualifiedJavadocReference toReference(
+ ResolvedReferenceTypeDeclaration type,
+ Optional member,
+ Optional memberType,
+ Optional label) {
+ return new FullyQualifiedJavadocReference(
+ type.getPackageName(),
+ Optional.of(type.getClassName()),
+ member,
+ memberType,
+ label,
+ !sourceModel.isInternal(type));
+ }
+
+ @Override
+ public String getStaticFieldValue(FullyQualifiedJavadocReference reference) {
+ String fqcn = reference
+ .getFullyQualifiedClassName()
+ .orElseThrow(() ->
+ new IllegalArgumentException("Given reference does not specify a fully qualified class name!"));
+ String fieldName = reference
+ .getMember()
+ .orElseThrow(() -> new IllegalArgumentException("Given reference does not specify a member!"));
+ TypeDeclaration> type = sourceModel
+ .getType(fqcn)
+ .orElseThrow(() -> new IllegalArgumentException("Could not find source class " + fqcn));
+ for (FieldDeclaration field : type.getFields()) {
+ Optional variable = field.getVariables().stream()
+ .filter(candidate -> candidate.getNameAsString().equals(fieldName))
+ .findFirst();
+ if (variable.isPresent()) {
+ if (!field.isStatic()) {
+ throw new IllegalArgumentException(
+ "Field with name " + fieldName + " in class " + fqcn + " is not static");
+ }
+ return variable.get()
+ .getInitializer()
+ .map(initializer -> initializer.toString())
+ .orElseThrow(() -> new IllegalArgumentException(
+ "Field with name " + fieldName + " in class " + fqcn + " has no initializer"));
+ }
}
+ throw new IllegalArgumentException("Could not find field with name " + fieldName + " in class " + fqcn);
+ }
+
+ @Override
+ public URI getInternalJavadocSiteBaseUrl() {
+ return linkGenerator.getInternalJavadocSiteBaseUrl();
+ }
+
+ private static ResolvedReferenceTypeDeclaration resolve(TypeDeclaration> declaration) {
+ return declaration.resolve();
+ }
+
+ private static String methodName(String member) {
+ int opening = member.indexOf('(');
+ return opening < 0 ? member : member.substring(0, opening);
+ }
+
+ private static String canonicalMember(ResolvedMethodLikeDeclaration declaration) {
+ return declaration.getName() + "("
+ + declaration.formalParameterTypes().stream()
+ .map(type -> type.erasure().describe())
+ .collect(Collectors.joining(","))
+ + ")";
+ }
+
+ private static String simpleName(String name) {
+ int separator = name.lastIndexOf('.');
+ return separator < 0 ? name : name.substring(separator + 1);
}
@SuppressWarnings("unchecked")
diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavadocBlockTagsToXhtmlConverter.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavadocBlockTagsToXhtmlConverter.java
index 327f67f75..884541097 100644
--- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavadocBlockTagsToXhtmlConverter.java
+++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavadocBlockTagsToXhtmlConverter.java
@@ -24,7 +24,6 @@
import java.util.Map;
-import com.thoughtworks.qdox.model.DocletTag;
import org.apache.maven.tools.plugin.extractor.annotations.converter.tag.block.JavadocBlockTagToHtmlConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,10 +51,6 @@ public JavadocBlockTagsToXhtmlConverter(
this.blockTagConverters = blockTagConverters;
}
- public String convert(DocletTag docletTag, ConverterContext context) {
- return convert(docletTag.getName(), docletTag.getValue(), context);
- }
-
public String convert(String name, String text, ConverterContext context) {
JavadocBlockTagToHtmlConverter converter = blockTagConverters.get(name);
if (converter == null) {
diff --git a/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaSourceModelTest.java b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaSourceModelTest.java
new file mode 100644
index 000000000..be6ab0c8d
--- /dev/null
+++ b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaSourceModelTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.maven.tools.plugin.extractor.annotations;
+
+import javax.tools.JavaCompiler;
+import javax.tools.ToolProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class JavaSourceModelTest {
+
+ @TempDir
+ Path sourceDirectory;
+
+ @Test
+ void acceptsCommentOnlyAndCurrentSyntax() throws Exception {
+ write("CommentOnly.java", "// no declaration\n");
+ write(
+ "example/Model.java",
+ "package example;\n"
+ + "import java.lang.annotation.ElementType;\n"
+ + "import java.lang.annotation.Target;\n"
+ + "import java.util.List;\n"
+ + "@Target(ElementType.TYPE_USE) @interface Nullable {}\n"
+ + "class Model { List<@Nullable String> values; }\n");
+
+ try (JavaSourceModel model = new JavaSourceModel(StandardCharsets.UTF_8)) {
+ model.addSourceDirectory(sourceDirectory.toFile());
+ model.parse();
+
+ assertTrue(model.getType("example.Model").isPresent());
+ assertTrue(model.resolveType("java.lang.String").isPresent());
+ }
+ }
+
+ @Test
+ void indexesNestedTypesAndExportedModules() throws Exception {
+ write("module-info.java", "module example.module { exports example; }\n");
+ write("example/Outer.java", "package example; public class Outer { public static class Nested {} }\n");
+
+ try (JavaSourceModel model = new JavaSourceModel(StandardCharsets.UTF_8)) {
+ model.addSourceDirectory(sourceDirectory.toFile());
+ model.parse();
+
+ assertTrue(model.getType("example.Outer.Nested").isPresent());
+ assertEquals("example.module", model.getModuleName("example").orElse(null));
+ assertTrue(model.hasPackage("example"));
+ }
+ }
+
+ @Test
+ void reportsSourcePathForInvalidJava() throws Exception {
+ Path source = write("example/Broken.java", "package example; class Broken {\n");
+
+ try (JavaSourceModel model = new JavaSourceModel(StandardCharsets.UTF_8)) {
+ model.addSourceDirectory(sourceDirectory.toFile());
+ IOException exception = assertThrows(IOException.class, model::parse);
+ assertTrue(exception.getMessage().contains(source.toString()));
+ }
+ }
+
+ @Test
+ void resolvesDirectoryClassesAgainstJarEntriesAndIndexesTheirPackages() throws Exception {
+ Path dependencySource = write(
+ sourceDirectory.resolve("dependency-sources"),
+ "dependency/Parent.java",
+ "package dependency; public class Parent {}\n");
+ Path dependencyClasses = sourceDirectory.resolve("dependency-classes");
+ compile(dependencySource, dependencyClasses);
+ Path dependencyJar = jar(dependencyClasses, sourceDirectory.resolve("dependency.jar"));
+
+ Path reactorSource = write(
+ sourceDirectory.resolve("reactor-sources"),
+ "reactor/Child.java",
+ "package reactor; public class Child extends dependency.Parent {}\n");
+ Path reactorClasses = sourceDirectory.resolve("reactor-classes");
+ compile(reactorSource, reactorClasses, dependencyJar);
+
+ try (JavaSourceModel model = new JavaSourceModel(StandardCharsets.UTF_8)) {
+ model.addClassPathEntry(reactorClasses.toFile());
+ model.addClassPathEntry(dependencyJar.toFile());
+ model.parse();
+
+ ResolvedReferenceTypeDeclaration child =
+ model.resolveType("reactor.Child").orElseThrow(AssertionError::new);
+ assertTrue(child.getAncestors().stream()
+ .anyMatch(ancestor -> "dependency.Parent".equals(ancestor.getQualifiedName())));
+ assertTrue(model.hasPackage("reactor"));
+ }
+ }
+
+ @Test
+ void resolvesClassesFromTheCompleteJavaClassLibrary() throws Exception {
+ try (JavaSourceModel model = new JavaSourceModel(StandardCharsets.UTF_8)) {
+ model.parse();
+
+ assertTrue(model.resolveType("org.w3c.dom.Document").isPresent());
+ assertTrue(model.resolveType("org.xml.sax.InputSource").isPresent());
+ }
+ }
+
+ private Path write(String relativePath, String source) throws IOException {
+ return write(sourceDirectory, relativePath, source);
+ }
+
+ private static Path write(Path directory, String relativePath, String source) throws IOException {
+ Path path = directory.resolve(relativePath);
+ Files.createDirectories(path.getParent());
+ Files.write(path, source.getBytes(StandardCharsets.UTF_8));
+ return path;
+ }
+
+ private static void compile(Path source, Path outputDirectory, Path... classPath) throws IOException {
+ Files.createDirectories(outputDirectory);
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ assertNotNull(compiler);
+ List arguments = new ArrayList<>();
+ arguments.add("-d");
+ arguments.add(outputDirectory.toString());
+ if (classPath.length > 0) {
+ arguments.add("-classpath");
+ arguments.add(Arrays.stream(classPath).map(Path::toString).collect(Collectors.joining(File.pathSeparator)));
+ }
+ arguments.add(source.toString());
+ assertEquals(0, compiler.run(null, null, null, arguments.toArray(new String[0])));
+ }
+
+ private static Path jar(Path classesDirectory, Path jarFile) throws IOException {
+ List classFiles;
+ try (Stream files = Files.walk(classesDirectory)) {
+ classFiles = files.filter(Files::isRegularFile).collect(Collectors.toList());
+ }
+ try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(jarFile))) {
+ for (Path classFile : classFiles) {
+ String name = classesDirectory.relativize(classFile).toString().replace(File.separatorChar, '/');
+ output.putNextEntry(new JarEntry(name));
+ Files.copy(classFile, output);
+ output.closeEntry();
+ }
+ }
+ return jarFile;
+ }
+}
diff --git a/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavaClassConverterContextTest.java b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavaClassConverterContextTest.java
index c6071f019..457060c57 100644
--- a/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavaClassConverterContextTest.java
+++ b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/converter/JavaClassConverterContextTest.java
@@ -20,11 +20,11 @@
import java.io.File;
import java.net.URI;
-import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
-import com.thoughtworks.qdox.JavaProjectBuilder;
-import com.thoughtworks.qdox.model.JavaClass;
+import com.github.javaparser.ast.body.TypeDeclaration;
+import org.apache.maven.tools.plugin.extractor.annotations.JavaSourceModel;
import org.apache.maven.tools.plugin.extractor.annotations.converter.test.CurrentClass;
import org.apache.maven.tools.plugin.extractor.annotations.converter.test.OtherClass;
import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent;
@@ -46,23 +46,24 @@ class JavaClassConverterContextTest {
private final String currentPackageName;
- private final JavaProjectBuilder builder;
+ private final JavaSourceModel sourceModel;
- private final JavaClass contextClass;
+ private final TypeDeclaration> contextClass;
private JavadocLinkGenerator linkGenerator;
private URI javadocBaseUri;
- JavaClassConverterContextTest() throws URISyntaxException {
- builder = new JavaProjectBuilder();
- builder.addSourceFolder(new File("src/test/java"));
+ JavaClassConverterContextTest() throws Exception {
+ sourceModel = new JavaSourceModel(StandardCharsets.UTF_8);
+ sourceModel.addSourceDirectory(new File("src/test/java"));
+ sourceModel.parse();
- contextClass = builder.getClassByName(CurrentClass.class.getName());
- currentPackageName = contextClass.getPackageName();
+ contextClass = sourceModel.getType(CurrentClass.class.getName()).orElseThrow(AssertionError::new);
+ currentPackageName = contextClass.resolve().getPackageName();
javadocBaseUri = new URI("http://localhost/apidocs");
linkGenerator = new JavadocLinkGenerator(javadocBaseUri, "11");
- context = new JavaClassConverterContext(contextClass, builder, Collections.emptyMap(), linkGenerator, 10);
+ context = new JavaClassConverterContext(contextClass, sourceModel, Collections.emptyMap(), linkGenerator, 10);
}
@Test
@@ -202,7 +203,7 @@ void getUrl() throws Exception {
MojoAnnotatedClass mojoAnnotatedClass = new MojoAnnotatedClass().setMojo(mojoAnnotationContent);
context = new JavaClassConverterContext(
contextClass,
- builder,
+ sourceModel,
Collections.singletonMap(
"org.apache.maven.tools.plugin.extractor.annotations.converter.test.OtherClass",
mojoAnnotatedClass),
diff --git a/pom.xml b/pom.xml
index ba5dbf80e..b438e9765 100644
--- a/pom.xml
+++ b/pom.xml
@@ -99,6 +99,7 @@
maven-plugin-tools-annotations/src/main/java/o/a/m/tools/plugins/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor#CLASS_VERSION_TO_JAVA_STRING
needs to be updated as well -->
9.10.1
+ 3.28.2
4.0.3
3.0.1
4.0.0
@@ -235,9 +236,9 @@
- com.thoughtworks.qdox
- qdox
- 2.2.0
+ com.github.javaparser
+ javaparser-symbol-solver-core
+ ${javaParserVersion}
@@ -345,6 +346,8 @@
**/it/source-encoding/
+
+ **/it/mplugin-421/**/CommentOnly.java
diff --git a/src/site/resources/images/plugin-tools-deps.svg b/src/site/resources/images/plugin-tools-deps.svg
index d0c2747cc..448bb3be1 100644
--- a/src/site/resources/images/plugin-tools-deps.svg
+++ b/src/site/resources/images/plugin-tools-deps.svg
@@ -69,11 +69,11 @@
-
-
-
-
- qdox
+
+
+
+
+ JavaParser