You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2023/06/08 16:18:21 UTC
[groovy] branch master updated: GROOVY-11088: stubgen: support `@PackageScope` for classes and fields
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 9e23c5f21f GROOVY-11088: stubgen: support `@PackageScope` for classes and fields
9e23c5f21f is described below
commit 9e23c5f21fb293707ae4d1d779d6606dc4b1be4c
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Thu Jun 8 10:31:08 2023 -0500
GROOVY-11088: stubgen: support `@PackageScope` for classes and fields
---
.../groovy/tools/javac/JavaStubGenerator.java | 274 +++++++++++++--------
.../groovy/tools/stubgenerator/Groovy11088.groovy | 47 ++++
.../tools/stubgenerator/Groovy11088pt2.groovy | 50 ++++
.../tools/stubgenerator/Groovy11088pt3.groovy | 50 ++++
4 files changed, 314 insertions(+), 107 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java
index a28cb9e58a..2db373365c 100644
--- a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java
+++ b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java
@@ -18,11 +18,14 @@
*/
package org.codehaus.groovy.tools.javac;
+import groovy.transform.PackageScope;
+import groovy.transform.PackageScopeTarget;
import org.apache.groovy.ast.tools.ExpressionUtils;
import org.apache.groovy.io.StringBuilderWriter;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.DynamicVariable;
import org.codehaus.groovy.ast.FieldNode;
@@ -102,6 +105,7 @@ import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
import static org.codehaus.groovy.ast.ClassHelper.isStaticConstantInitializerType;
import static org.codehaus.groovy.ast.ClassHelper.isStringType;
+import static org.codehaus.groovy.ast.ClassHelper.makeCached;
import static org.codehaus.groovy.ast.tools.GeneralUtils.defaultValueX;
import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpecRecurse;
@@ -112,10 +116,11 @@ import static org.codehaus.groovy.ast.tools.WideningCategories.isLongCategory;
public class JavaStubGenerator {
private final String encoding;
- private final boolean requireSuperResolved;
private final File outputPath;
+ private final boolean requireSuperResolved;
private final List<ConstructorNode> constructors = new ArrayList<>();
private final Map<String, MethodNode> propertyMethods = new LinkedHashMap<>();
+ private final static ClassNode PACKAGE_SCOPE_TYPE = makeCached(PackageScope.class);
private ModuleNode currentModule;
@@ -205,11 +210,11 @@ public class JavaStubGenerator {
return writer.toString();
}
- private static Iterable<ClassNode> findTraits(ClassNode node) {
+ private static Iterable<ClassNode> findTraits(ClassNode classNode) {
Set<ClassNode> traits = new LinkedHashSet<>();
LinkedList<ClassNode> todo = new LinkedList<>();
- Collections.addAll(todo, node.getInterfaces());
+ Collections.addAll(todo, classNode.getInterfaces());
while (!todo.isEmpty()) {
ClassNode next = todo.removeLast();
if (Traits.isTrait(next)) traits.add(next);
@@ -328,13 +333,17 @@ public class JavaStubGenerator {
classNode.getDeclaredConstructors().clear();
}
- boolean isInterface = isInterfaceOrTrait(classNode);
boolean isEnum = classNode.isEnum();
+ boolean isInterface = !isEnum && isInterfaceOrTrait(classNode);
boolean isAnnotationDefinition = classNode.isAnnotationDefinition();
printAnnotations(out, classNode);
- printModifiers(out, classNode.getModifiers()
- & ~(isInterface ? Opcodes.ACC_ABSTRACT : 0)
- & ~(isEnum ? Opcodes.ACC_FINAL | Opcodes.ACC_ABSTRACT : 0));
+
+ int flags = classNode.getModifiers();
+ if (isEnum) flags &= ~Opcodes.ACC_FINAL;
+ if (isEnum || isInterface) flags &= ~Opcodes.ACC_ABSTRACT;
+ if (classNode.isSyntheticPublic() && hasPackageScopeXform(classNode,
+ PackageScopeTarget.CLASS)) flags &= ~Opcodes.ACC_PUBLIC;
+ printModifiers(out, flags);
if (isInterface) {
if (isAnnotationDefinition) {
@@ -377,7 +386,7 @@ public class JavaStubGenerator {
}
out.println(" {");
- printFields(out, classNode);
+ printFields(out, classNode, isInterface);
printMethods(out, classNode, isEnum);
for (Iterator<InnerClassNode> inner = classNode.getInnerClasses(); inner.hasNext(); ) {
@@ -394,99 +403,31 @@ public class JavaStubGenerator {
}
}
- private void printMethods(PrintWriter out, ClassNode classNode, boolean isEnum) {
- if (!isEnum) printConstructors(out, classNode);
-
- List<MethodNode> methods = new ArrayList<>(propertyMethods.values());
- methods.addAll(classNode.getMethods());
- for (MethodNode method : methods) {
- if (isEnum && method.isSynthetic()) {
- // skip values() method and valueOf(String)
- String name = method.getName();
- Parameter[] params = method.getParameters();
- if (params.length == 0 && name.equals("values")) continue;
- if (params.length == 1
- && name.equals("valueOf")
- && isStringType(params[0].getType())) {
- continue;
- }
- }
- printMethod(out, classNode, method);
- }
-
- // print the methods from traits
- for (ClassNode trait : findTraits(classNode)) {
- Map<String, ClassNode> generics = trait.isUsingGenerics() ? createGenericsSpec(trait) : null;
- List<MethodNode> traitMethods = trait.getMethods();
- for (MethodNode traitOrigMethod : traitMethods) {
- // GROOVY-9606: replace method return type and parameter type placeholder with resolved type from trait generics
- MethodNode traitMethod = correctToGenericsSpec(generics, traitOrigMethod);
- MethodNode existingMethod = classNode.getMethod(traitMethod.getName(), traitMethod.getParameters());
- if (existingMethod != null) continue;
- for (MethodNode propertyMethod : propertyMethods.values()) {
- if (propertyMethod.getName().equals(traitMethod.getName())) {
- boolean sameParams = sameParameterTypes(propertyMethod, traitMethod);
- if (sameParams) {
- existingMethod = propertyMethod;
- break;
- }
- }
- }
- if (existingMethod == null && isConcreteTraitMethod(trait, traitMethod)) {
- printMethod(out, classNode, traitMethod);
- }
- }
- }
- }
-
- private static boolean isConcreteTraitMethod(final ClassNode trait, final MethodNode traitMethod) {
- if (!(trait.redirect() instanceof DecompiledClassNode)) {
- return !traitMethod.isAbstract();
- }
- boolean isSynthetic = (traitMethod.getModifiers() & Opcodes.ACC_SYNTHETIC) != 0;
- if (!isSynthetic && !traitMethod.getName().contains("$")) {
- for (MethodNode helperMethod : Traits.findHelper(trait).getMethods(traitMethod.getName())) {
- Parameter[] params = helperMethod.getParameters();
- params = Arrays.copyOfRange(params, 1, params.length);
- if (sameParameterTypes(params, traitMethod.getParameters())) return true;
- }
- }
- return false;
- }
-
- private static boolean sameParameterTypes(final MethodNode firstMethod, final MethodNode secondMethod) {
- return sameParameterTypes(firstMethod.getParameters(), secondMethod.getParameters());
- }
-
- private static boolean sameParameterTypes(final Parameter[] firstParams, final Parameter[] secondParams) {
- return org.codehaus.groovy.ast.tools.ParameterUtils.parametersEqual(firstParams, secondParams);
- }
-
- private void printConstructors(final PrintWriter out, final ClassNode classNode) {
- List<ConstructorNode> constructors = new ArrayList<>(this.constructors);
- constructors.addAll(classNode.getDeclaredConstructors());
- for (ConstructorNode constructor : constructors) {
- printConstructor(out, classNode, constructor);
- }
- }
-
- private void printFields(final PrintWriter out, final ClassNode classNode) {
+ private void printFields(PrintWriter out, ClassNode classNode, boolean ifaceOrTrait) {
List<FieldNode> fields = classNode.getFields();
if (!fields.isEmpty()) {
- List<FieldNode> enumFields = new LinkedList<>();
- List<FieldNode> normalFields = new LinkedList<>();
+ List<FieldNode> enumFields = new ArrayList<>();
+ List<FieldNode> normalFields = new ArrayList<>();
for (FieldNode field : fields) {
- if (field.isEnum()) {
+ int flags = field.getModifiers();
+ if (hasPackageScopeXform(field, PackageScopeTarget.FIELDS)){
+ flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC);
+ List<AnnotationNode> annotations = field.getAnnotations();
+ field = new FieldNode(field.getName(), flags, field.getType(), field.getOwner(), field.getInitialExpression());
+ field.setDeclaringClass(classNode);
+ field.addAnnotations(annotations);
+ }
+
+ if ((flags & Opcodes.ACC_ENUM) != 0) {
enumFields.add(field);
- } else if (!field.isPrivate() && (field.getModifiers() & Opcodes.ACC_SYNTHETIC) == 0) {
+ } else if ((flags & (Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC)) == 0) {
normalFields.add(field);
}
}
- boolean interfaceOrTrait = isInterfaceOrTrait(classNode);
printEnumFields(out, enumFields);
for (FieldNode normalField : normalFields) {
- printField(out, normalField, interfaceOrTrait);
+ printField(out, normalField, ifaceOrTrait);
}
}
}
@@ -504,9 +445,9 @@ public class JavaStubGenerator {
out.println(';');
}
- private void printField(final PrintWriter out, final FieldNode field, final boolean fromFaceOrTrait) {
+ private void printField(final PrintWriter out, final FieldNode field, final boolean ifaceOrTrait) {
printAnnotations(out, field);
- if (!fromFaceOrTrait) {
+ if (!ifaceOrTrait) {
printModifiers(out, field.getModifiers());
}
ClassNode type = field.getType();
@@ -514,7 +455,7 @@ public class JavaStubGenerator {
out.print(' ');
out.print(field.getName());
- if (fromFaceOrTrait || field.isFinal()) {
+ if (ifaceOrTrait || field.isFinal()) {
out.print(" = ");
if (field.isStatic()) {
Expression value = ExpressionUtils.transformInlineConstants(field.getInitialValueExpression(), type);
@@ -556,6 +497,82 @@ public class JavaStubGenerator {
out.println(';');
}
+ private void printMethods(final PrintWriter out, final ClassNode classNode, final boolean isEnum) {
+ if (!isEnum) printConstructors(out, classNode);
+
+ List<MethodNode> methods = new ArrayList<>(propertyMethods.values());
+ methods.addAll(classNode.getMethods());
+ for (MethodNode method : methods) {
+ if (isEnum && method.isSynthetic()) {
+ // skip values() method and valueOf(String)
+ String name = method.getName();
+ Parameter[] params = method.getParameters();
+ if (params.length == 0 && name.equals("values")) continue;
+ if (params.length == 1
+ && name.equals("valueOf")
+ && isStringType(params[0].getType())) {
+ continue;
+ }
+ }
+ printMethod(out, classNode, method);
+ }
+
+ // print the methods from traits
+ for (ClassNode trait : findTraits(classNode)) {
+ Map<String, ClassNode> generics = trait.isUsingGenerics() ? createGenericsSpec(trait) : null;
+ List<MethodNode> traitMethods = trait.getMethods();
+ for (MethodNode traitOrigMethod : traitMethods) {
+ // GROOVY-9606: replace method return type and parameter type placeholder with resolved type from trait generics
+ MethodNode traitMethod = correctToGenericsSpec(generics, traitOrigMethod);
+ MethodNode existingMethod = classNode.getMethod(traitMethod.getName(), traitMethod.getParameters());
+ if (existingMethod != null) continue;
+ for (MethodNode propertyMethod : propertyMethods.values()) {
+ if (propertyMethod.getName().equals(traitMethod.getName())) {
+ boolean sameParams = sameParameterTypes(propertyMethod, traitMethod);
+ if (sameParams) {
+ existingMethod = propertyMethod;
+ break;
+ }
+ }
+ }
+ if (existingMethod == null && isConcreteTraitMethod(trait, traitMethod)) {
+ printMethod(out, classNode, traitMethod);
+ }
+ }
+ }
+ }
+
+ private static boolean isConcreteTraitMethod(final ClassNode trait, final MethodNode traitMethod) {
+ if (!(trait.redirect() instanceof DecompiledClassNode)) {
+ return !traitMethod.isAbstract();
+ }
+ boolean isSynthetic = (traitMethod.getModifiers() & Opcodes.ACC_SYNTHETIC) != 0;
+ if (!isSynthetic && !traitMethod.getName().contains("$")) {
+ for (MethodNode helperMethod : Traits.findHelper(trait).getMethods(traitMethod.getName())) {
+ Parameter[] params = helperMethod.getParameters();
+ params = Arrays.copyOfRange(params, 1, params.length);
+ if (sameParameterTypes(params, traitMethod.getParameters())) return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean sameParameterTypes(final MethodNode firstMethod, final MethodNode secondMethod) {
+ return sameParameterTypes(firstMethod.getParameters(), secondMethod.getParameters());
+ }
+
+ private static boolean sameParameterTypes(final Parameter[] firstParams, final Parameter[] secondParams) {
+ return org.codehaus.groovy.ast.tools.ParameterUtils.parametersEqual(firstParams, secondParams);
+ }
+
+ private void printConstructors(final PrintWriter out, final ClassNode classNode) {
+ List<ConstructorNode> constructors = new ArrayList<>(this.constructors);
+ constructors.addAll(classNode.getDeclaredConstructors());
+ for (ConstructorNode constructor : constructors) {
+ printConstructor(out, classNode, constructor);
+ }
+ }
+
private void printConstructor(PrintWriter out, ClassNode clazz, ConstructorNode constructorNode) {
printAnnotations(out, constructorNode);
// printModifiers(out, constructorNode.getModifiers());
@@ -962,7 +979,8 @@ public class JavaStubGenerator {
private void printAnnotations(final PrintWriter out, final AnnotatedNode annotated) {
for (AnnotationNode annotation : annotated.getAnnotations()) {
- printAnnotation(out, annotation);
+ if (!annotation.getClassNode().equals(PACKAGE_SCOPE_TYPE))
+ printAnnotation(out, annotation);
}
}
@@ -1060,21 +1078,11 @@ public class JavaStubGenerator {
out.println();
}
- public void clean() {
- Stream<JavaFileObject> javaFileObjectStream =
- javaStubCompilationUnitSet.size() < 2
- ? javaStubCompilationUnitSet.stream()
- : javaStubCompilationUnitSet.parallelStream();
-
- javaFileObjectStream.forEach(FileObject::delete);
- javaStubCompilationUnitSet.clear();
- }
-
- private File createJavaStubFile(String path) {
+ private File createJavaStubFile(final String path) {
return new File(outputPath, path + ".java");
}
- private static String escapeSpecialChars(String value) {
+ private static String escapeSpecialChars(final String value) {
return FormatHelper.escapeBackslashes(value).replace("\"", "\\\"");
}
@@ -1082,9 +1090,61 @@ public class JavaStubGenerator {
return cn.isInterface() || Traits.isTrait(cn);
}
+ private boolean hasPackageScopeXform(final AnnotatedNode node, final PackageScopeTarget type) {
+ boolean member = (!(node instanceof ClassNode) && type != PackageScopeTarget.CLASS);
+ for (AnnotationNode anno : node.getAnnotations()) {
+ if (anno.getClassNode().equals(PACKAGE_SCOPE_TYPE)) {
+ Expression expr = anno.getMember("value");
+ if (expr == null) {
+ // if empty @PackageScope, node type and target type must be in alignment
+ return member || (node instanceof ClassNode && type == PackageScopeTarget.CLASS);
+ }
+
+ final boolean[] val = new boolean[1];
+ expr.visit(new CodeVisitorSupport() {
+ @Override
+ public void visitPropertyExpression(final PropertyExpression property) {
+ if (property.getObjectExpression().getText().equals("groovy.transform.PackageScopeTarget")
+ && property.getPropertyAsString().equals(type.name())) {
+ val[0] = true;
+ }
+ }
+ @Override
+ public void visitVariableExpression(final VariableExpression variable) {
+ if (variable.getName().equals(type.name())) {
+ ImportNode imp = currentModule.getStaticImports().get(type.name());
+ if (imp != null && imp.getType().getName().equals("groovy.transform.PackageScopeTarget")) {
+ val[0] = true;
+ } else if (imp == null && currentModule.getStaticStarImports().get("groovy.transform.PackageScopeTarget") != null) {
+ val[0] = true;
+ }
+ }
+ }
+ });
+ return val[0];
+ }
+ }
+ if (member) { // check for @PackageScope(XXX) on class
+ return hasPackageScopeXform(node.getDeclaringClass(), type);
+ }
+ return false;
+ }
+
+ //--------------------------------------------------------------------------
+
private final Set<JavaFileObject> javaStubCompilationUnitSet = new HashSet<>();
public Set<JavaFileObject> getJavaStubCompilationUnitSet() {
return javaStubCompilationUnitSet;
}
+
+ public void clean() {
+ Stream<JavaFileObject> javaFileObjectStream =
+ javaStubCompilationUnitSet.size() < 2
+ ? javaStubCompilationUnitSet.stream()
+ : javaStubCompilationUnitSet.parallelStream();
+
+ javaFileObjectStream.forEach(FileObject::delete);
+ javaStubCompilationUnitSet.clear();
+ }
}
diff --git a/src/test/org/codehaus/groovy/tools/stubgenerator/Groovy11088.groovy b/src/test/org/codehaus/groovy/tools/stubgenerator/Groovy11088.groovy
new file mode 100644
index 0000000000..12dc06618c
--- /dev/null
+++ b/src/test/org/codehaus/groovy/tools/stubgenerator/Groovy11088.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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.codehaus.groovy.tools.stubgenerator
+
+final class Groovy11088 extends StringSourcesStubTestCase {
+
+ @Override
+ Map<String, String> provideSources() {
+ [
+ 'C.groovy': '''import groovy.transform.*
+ class C {
+ @Deprecated @PackageScope
+ final static String VALUE = "x"
+ }
+ ''',
+ 'Main.java': '''
+ public class Main {
+ public static void main(String[] args) {
+ System.err.println(C.VALUE);
+ }
+ }
+ ''',
+ ]
+ }
+
+ @Override
+ void verifyStubs() {
+ String stub = stubJavaSourceFor('C')
+ assert stub.contains('@java.lang.Deprecated() static final java.lang.String VALUE = "x";')
+ }
+}
diff --git a/src/test/org/codehaus/groovy/tools/stubgenerator/Groovy11088pt2.groovy b/src/test/org/codehaus/groovy/tools/stubgenerator/Groovy11088pt2.groovy
new file mode 100644
index 0000000000..cbcb5deb74
--- /dev/null
+++ b/src/test/org/codehaus/groovy/tools/stubgenerator/Groovy11088pt2.groovy
@@ -0,0 +1,50 @@
+/*
+ * 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.codehaus.groovy.tools.stubgenerator
+
+final class Groovy11088pt2 extends StringSourcesStubTestCase {
+
+ @Override
+ Map<String, String> provideSources() {
+ [
+ 'C.groovy': '''import groovy.transform.*
+ @PackageScope([
+ PackageScopeTarget.FIELDS,
+ PackageScopeTarget.METHODS
+ ])
+ class C {
+ @Deprecated static String VALUE = "x"
+ }
+ ''',
+ 'Main.java': '''
+ public class Main {
+ public static void main(String[] args) {
+ System.err.println(C.VALUE);
+ }
+ }
+ ''',
+ ]
+ }
+
+ @Override
+ void verifyStubs() {
+ String stub = stubJavaSourceFor('C')
+ assert stub.contains('@java.lang.Deprecated() static java.lang.String VALUE;')
+ }
+}
diff --git a/src/test/org/codehaus/groovy/tools/stubgenerator/Groovy11088pt3.groovy b/src/test/org/codehaus/groovy/tools/stubgenerator/Groovy11088pt3.groovy
new file mode 100644
index 0000000000..62a6a27bf5
--- /dev/null
+++ b/src/test/org/codehaus/groovy/tools/stubgenerator/Groovy11088pt3.groovy
@@ -0,0 +1,50 @@
+/*
+ * 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.codehaus.groovy.tools.stubgenerator
+
+final class Groovy11088pt3 extends StringSourcesStubTestCase {
+
+ @Override
+ Map<String, String> provideSources() {
+ [
+ 'C.groovy': '''import groovy.transform.*
+ @Deprecated @PackageScope
+ class C {
+ @Deprecated @PackageScope
+ class D {
+ }
+ }
+ ''',
+ 'Main.java': '''
+ public class Main {
+ public static void main(String[] args) {
+ new C();
+ }
+ }
+ ''',
+ ]
+ }
+
+ @Override
+ void verifyStubs() {
+ String stub = stubJavaSourceFor('C')
+ assert stub.contains('@java.lang.Deprecated() class C')
+ assert stub.contains('@java.lang.Deprecated() class D')
+ }
+}