[
https://issues.apache.org/jira/browse/GROOVY-11913?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18072172#comment-18072172
]
ASF GitHub Bot commented on GROOVY-11913:
-----------------------------------------
Copilot commented on code in PR #2450:
URL: https://github.com/apache/groovy/pull/2450#discussion_r3055877174
##########
build-logic/src/main/groovy/org/apache/groovy/gradle/SwingBuilderWidgetDocTask.groovy:
##########
@@ -0,0 +1,85 @@
+/*
+ * 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.groovy.gradle
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Classpath
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.process.ExecOperations
+
+import javax.inject.Inject
+
+/**
+ * Generates AsciiDoc documentation for SwingBuilder widgets by introspecting
+ * the SwingBuilder class from the project being built.
+ *
+ * The generator script is bundled as a resource in the build-logic jar.
+ * At execution time it is extracted to a temporary file and run via
+ * {@code groovy.ui.GroovyMain} in a forked JVM whose classpath contains the
+ * groovy-swing classes being built (plus the core Groovy jar they depend on).
+ */
+@CacheableTask
+@CompileStatic
+class SwingBuilderWidgetDocTask extends DefaultTask {
+
+ private static final String SCRIPT_RESOURCE =
'org/apache/groovy/gradle/GenerateSwingBuilderWidgetDocs.groovy'
+
+ private final ExecOperations execOperations
+
+ @InputFiles
+ @Classpath
+ final ConfigurableFileCollection classpath =
project.objects.fileCollection()
+
+ @OutputDirectory
+ final DirectoryProperty outputDirectory =
project.objects.directoryProperty()
+
+ @Inject
+ SwingBuilderWidgetDocTask(ExecOperations execOperations) {
+ this.execOperations = execOperations
+ description = 'Generates AsciiDoc widget reference for SwingBuilder.'
+ group = 'documentation'
+ }
+
+ @TaskAction
+ @CompileDynamic
+ void generate() {
+ outputDirectory.get().asFile.mkdirs()
+
+ // Extract the generator script from build-logic resources to a temp
file
+ File scriptFile =
File.createTempFile('GenerateSwingBuilderWidgetDocs', '.groovy')
+ scriptFile.deleteOnExit()
+ getClass().classLoader.getResourceAsStream(SCRIPT_RESOURCE).withStream
{ input ->
+ scriptFile.bytes = input.bytes
+ }
+
+ execOperations.javaexec {
+ it.mainClass.set('groovy.ui.GroovyMain')
+ it.classpath = this.classpath
+ it.jvmArgs('-Djava.awt.headless=true')
+ it.args(scriptFile.absolutePath,
outputDirectory.asFile.get().absolutePath)
Review Comment:
Using `File.createTempFile(...)` with `deleteOnExit()` can leak temp files
under the Gradle daemon (the JVM may not exit for a long time). Prefer creating
the script under Gradle’s `temporaryDir` and deleting it after execution (or
reuse a stable file name in `temporaryDir`).
```suggestion
// Extract the generator script from build-logic resources to the
task temporary directory
temporaryDir.mkdirs()
File scriptFile = new File(temporaryDir,
'GenerateSwingBuilderWidgetDocs.groovy')
getClass().classLoader.getResourceAsStream(SCRIPT_RESOURCE).withStream { input
->
scriptFile.bytes = input.bytes
}
try {
execOperations.javaexec {
it.mainClass.set('groovy.ui.GroovyMain')
it.classpath = this.classpath
it.jvmArgs('-Djava.awt.headless=true')
it.args(scriptFile.absolutePath,
outputDirectory.asFile.get().absolutePath)
}
} finally {
scriptFile.delete()
```
##########
build-logic/src/main/resources/org/apache/groovy/gradle/GenerateSwingBuilderWidgetDocs.groovy:
##########
@@ -0,0 +1,361 @@
+/*
+ * 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.
+ */
+
+/**
+ * Generates AsciiDoc documentation for SwingBuilder widgets.
+ *
+ * This script introspects a live SwingBuilder instance to discover all
+ * registered nodes, their categories, underlying Swing classes, and
+ * JavaBeans properties. It produces a single file
+ * (_swing-builder-widgets.adoc) containing category summary tables
+ * followed by detail sections for each widget.
+ *
+ * Usage:
+ * groovy GenerateSwingBuilderWidgetDocs.groovy [outputDir]
+ *
+ * If outputDir is omitted, output goes to src/spec/doc under this subproject.
+ */
+
+import groovy.swing.SwingBuilder
+import groovy.swing.factory.BeanFactory
+import groovy.swing.factory.ComponentFactory
+import groovy.swing.factory.RichActionWidgetFactory
+import groovy.swing.factory.TextArgWidgetFactory
+import groovy.swing.factory.FormattedTextFactory
+import groovy.swing.factory.LayoutFactory
+import groovy.swing.factory.TabbedPaneFactory
+import groovy.swing.factory.WidgetFactory
Review Comment:
These imports appear unused in the script (no references in the file).
Removing them will reduce noise and avoid implying behavior that isn’t actually
present.
```suggestion
```
##########
build-logic/src/main/resources/org/apache/groovy/gradle/GenerateSwingBuilderWidgetDocs.groovy:
##########
@@ -0,0 +1,361 @@
+/*
+ * 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.
+ */
+
+/**
+ * Generates AsciiDoc documentation for SwingBuilder widgets.
+ *
+ * This script introspects a live SwingBuilder instance to discover all
+ * registered nodes, their categories, underlying Swing classes, and
+ * JavaBeans properties. It produces a single file
+ * (_swing-builder-widgets.adoc) containing category summary tables
+ * followed by detail sections for each widget.
+ *
+ * Usage:
+ * groovy GenerateSwingBuilderWidgetDocs.groovy [outputDir]
+ *
+ * If outputDir is omitted, output goes to src/spec/doc under this subproject.
+ */
+
+import groovy.swing.SwingBuilder
+import groovy.swing.factory.BeanFactory
+import groovy.swing.factory.ComponentFactory
+import groovy.swing.factory.RichActionWidgetFactory
+import groovy.swing.factory.TextArgWidgetFactory
+import groovy.swing.factory.FormattedTextFactory
+import groovy.swing.factory.LayoutFactory
+import groovy.swing.factory.TabbedPaneFactory
+import groovy.swing.factory.WidgetFactory
+
+import java.beans.BeanInfo
+import java.beans.Introspector
+import java.beans.PropertyDescriptor
+
+// Headless mode should be set via -Djava.awt.headless=true on the JVM command
line
+if (!Boolean.getBoolean('java.awt.headless')) {
+ System.setProperty('java.awt.headless', 'true')
+}
+
+def outputDir = args.length > 0 ? new File(args[0]) : new File('src/spec/doc')
+outputDir.mkdirs()
+
+def builder = new SwingBuilder()
+
+// Collect all factories keyed by node name
+Map<String, groovy.util.Factory> factories = builder.factories
+
+// Collect registration groups (categories) - these come from the register*()
method names
+Map<String, Set<String>> groups = [:]
+builder.registrationGroups.each { groupName ->
+ def items = builder.getRegistrationGroupItems(groupName)
+ if (items && groupName) {
+ groups[groupName] = items
+ }
+}
+
+// Also collect explicit methods (edt, doLater, etc.)
+Map<String, Closure> explicitMethods = builder.localExplicitMethods
+
+// Human-friendly category names derived from method name suffixes
+def categoryLabel = { String groupName ->
+ // e.g. "ActionButtonWidgets" -> "Action Button Widgets"
+ groupName.replaceAll(/([a-z])([A-Z])/, '$1 $2')
Review Comment:
`categoryLabel` only inserts spaces between a lowercase letter followed by
an uppercase letter, which leaves acronym-prefixed groups like `MDIWidgets`
unformatted in the generated docs (currently rendered as “MDIWidgets”).
Consider enhancing the splitting logic to also handle acronym boundaries so
headings are consistently human-readable.
```suggestion
// and "MDIWidgets" -> "MDI Widgets"
groupName
.replaceAll(/([A-Z]+)([A-Z][a-z])/, '$1 $2')
.replaceAll(/([a-z])([A-Z])/, '$1 $2')
```
##########
build-logic/src/main/resources/org/apache/groovy/gradle/GenerateSwingBuilderWidgetDocs.groovy:
##########
@@ -0,0 +1,361 @@
+/*
+ * 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.
+ */
+
+/**
+ * Generates AsciiDoc documentation for SwingBuilder widgets.
+ *
+ * This script introspects a live SwingBuilder instance to discover all
+ * registered nodes, their categories, underlying Swing classes, and
+ * JavaBeans properties. It produces a single file
+ * (_swing-builder-widgets.adoc) containing category summary tables
+ * followed by detail sections for each widget.
+ *
+ * Usage:
+ * groovy GenerateSwingBuilderWidgetDocs.groovy [outputDir]
+ *
+ * If outputDir is omitted, output goes to src/spec/doc under this subproject.
+ */
+
+import groovy.swing.SwingBuilder
+import groovy.swing.factory.BeanFactory
+import groovy.swing.factory.ComponentFactory
+import groovy.swing.factory.RichActionWidgetFactory
+import groovy.swing.factory.TextArgWidgetFactory
+import groovy.swing.factory.FormattedTextFactory
+import groovy.swing.factory.LayoutFactory
+import groovy.swing.factory.TabbedPaneFactory
+import groovy.swing.factory.WidgetFactory
+
+import java.beans.BeanInfo
+import java.beans.Introspector
+import java.beans.PropertyDescriptor
+
+// Headless mode should be set via -Djava.awt.headless=true on the JVM command
line
+if (!Boolean.getBoolean('java.awt.headless')) {
+ System.setProperty('java.awt.headless', 'true')
+}
+
+def outputDir = args.length > 0 ? new File(args[0]) : new File('src/spec/doc')
+outputDir.mkdirs()
+
+def builder = new SwingBuilder()
+
+// Collect all factories keyed by node name
+Map<String, groovy.util.Factory> factories = builder.factories
+
+// Collect registration groups (categories) - these come from the register*()
method names
+Map<String, Set<String>> groups = [:]
+builder.registrationGroups.each { groupName ->
+ def items = builder.getRegistrationGroupItems(groupName)
+ if (items && groupName) {
+ groups[groupName] = items
+ }
+}
+
+// Also collect explicit methods (edt, doLater, etc.)
+Map<String, Closure> explicitMethods = builder.localExplicitMethods
+
+// Human-friendly category names derived from method name suffixes
+def categoryLabel = { String groupName ->
+ // e.g. "ActionButtonWidgets" -> "Action Button Widgets"
+ groupName.replaceAll(/([a-z])([A-Z])/, '$1 $2')
+}
+
+/**
+ * Known Swing class mappings for factories that don't expose the class via a
standard field.
+ * This covers factories extending AbstractFactory directly (e.g.
RootPaneContainerFactory subclasses)
+ * and custom factories with non-standard constructors.
+ */
+def knownSwingClasses = [
+ 'frame' : javax.swing.JFrame,
+ 'dialog' : javax.swing.JDialog,
+ 'window' : javax.swing.JWindow,
+ 'internalFrame' : javax.swing.JInternalFrame,
+ 'action' : javax.swing.Action,
+ 'imageIcon' : javax.swing.ImageIcon,
+ 'comboBox' : javax.swing.JComboBox,
+ 'list' : javax.swing.JList,
+ 'separator' : javax.swing.JSeparator,
+ 'scrollPane' : javax.swing.JScrollPane,
+ 'splitPane' : javax.swing.JSplitPane,
+ 'table' : javax.swing.JTable,
+ 'formattedTextField': javax.swing.JFormattedTextField,
+ 'box' : javax.swing.Box,
+ 'hbox' : javax.swing.Box,
+ 'vbox' : javax.swing.Box,
+]
+
+/**
+ * Determine the underlying Swing/AWT class for a given factory.
+ */
+def resolveSwingClass = { String nodeName, groovy.util.Factory factory ->
+ if (factory == null) return null
+
+ // Check known mappings first
+ if (knownSwingClasses.containsKey(nodeName)) {
+ return knownSwingClasses[nodeName]
+ }
+
+ // BeanFactory and subclasses (ComponentFactory, TabbedPaneFactory, etc.)
store beanClass
+ if (factory instanceof BeanFactory) {
+ return factory.beanClass
+ }
+ // RichActionWidgetFactory stores klass
+ if (factory instanceof RichActionWidgetFactory) {
+ return factory.klass
+ }
+ // TextArgWidgetFactory extends RichActionWidgetFactory
+ if (factory instanceof TextArgWidgetFactory) {
+ return factory.klass
+ }
Review Comment:
`TextArgWidgetFactory` extends `RichActionWidgetFactory`, so the `instanceof
TextArgWidgetFactory` branch is unreachable because the
`RichActionWidgetFactory` check above will already match. Remove the redundant
branch or check `TextArgWidgetFactory` first if it needs special handling.
```suggestion
// RichActionWidgetFactory and subclasses (including
TextArgWidgetFactory) store klass
if (factory instanceof RichActionWidgetFactory) {
return factory.klass
}
```
##########
build-logic/src/main/groovy/org/apache/groovy/gradle/SwingBuilderWidgetDocTask.groovy:
##########
@@ -0,0 +1,85 @@
+/*
+ * 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.groovy.gradle
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Classpath
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.process.ExecOperations
+
+import javax.inject.Inject
+
+/**
+ * Generates AsciiDoc documentation for SwingBuilder widgets by introspecting
+ * the SwingBuilder class from the project being built.
+ *
+ * The generator script is bundled as a resource in the build-logic jar.
+ * At execution time it is extracted to a temporary file and run via
+ * {@code groovy.ui.GroovyMain} in a forked JVM whose classpath contains the
+ * groovy-swing classes being built (plus the core Groovy jar they depend on).
+ */
+@CacheableTask
+@CompileStatic
+class SwingBuilderWidgetDocTask extends DefaultTask {
+
+ private static final String SCRIPT_RESOURCE =
'org/apache/groovy/gradle/GenerateSwingBuilderWidgetDocs.groovy'
+
+ private final ExecOperations execOperations
+
+ @InputFiles
+ @Classpath
+ final ConfigurableFileCollection classpath =
project.objects.fileCollection()
+
+ @OutputDirectory
+ final DirectoryProperty outputDirectory =
project.objects.directoryProperty()
+
+ @Inject
+ SwingBuilderWidgetDocTask(ExecOperations execOperations) {
+ this.execOperations = execOperations
+ description = 'Generates AsciiDoc widget reference for SwingBuilder.'
+ group = 'documentation'
+ }
+
+ @TaskAction
+ @CompileDynamic
+ void generate() {
+ outputDirectory.get().asFile.mkdirs()
+
+ // Extract the generator script from build-logic resources to a temp
file
+ File scriptFile =
File.createTempFile('GenerateSwingBuilderWidgetDocs', '.groovy')
+ scriptFile.deleteOnExit()
+ getClass().classLoader.getResourceAsStream(SCRIPT_RESOURCE).withStream
{ input ->
Review Comment:
`getResourceAsStream(SCRIPT_RESOURCE)` can return null (e.g., if the
resource isn’t packaged as expected), and calling `withStream` on null will
throw an unhelpful NPE. Please add an explicit null check and fail with a clear
exception message if the generator script resource cannot be found.
```suggestion
def scriptResourceStream =
getClass().classLoader.getResourceAsStream(SCRIPT_RESOURCE)
if (scriptResourceStream == null) {
throw new IllegalStateException("Generator script resource not
found: ${SCRIPT_RESOURCE}")
}
scriptResourceStream.withStream { input ->
```
> Bring back SwingBuilder widgets list
> ------------------------------------
>
> Key: GROOVY-11913
> URL: https://issues.apache.org/jira/browse/GROOVY-11913
> Project: Groovy
> Issue Type: Improvement
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
> Fix For: 6.0.0-alpha-1
>
>
> This is to fix the following issue raised on the groovy-website repo:
> https://github.com/apache/groovy-website/issues/46
> This assumes we need to produce versions of the widget list for a particular
> groovy version. The list of widgets changes very rarely but could change We
> can delete this issue if that proves not useful.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)