This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch jline3
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/jline3 by this push:
     new cce392c1d0 support records and interfaces amd static imports
cce392c1d0 is described below

commit cce392c1d02d291e2cb686011a6b75ea9a15f840
Author: Paul King <pa...@asert.com.au>
AuthorDate: Thu Jul 3 20:25:58 2025 +1000

    support records and interfaces amd static imports
---
 .../apache/groovy/groovysh/jline/GroovyEngine.java | 69 ++++++++++++++++++----
 .../groovysh/commands/ImportCommandTest.groovy     | 50 ----------------
 .../groovy/groovysh/commands/ImportTest.groovy     | 30 ++++++++++
 3 files changed, 88 insertions(+), 61 deletions(-)

diff --git 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyEngine.java
 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyEngine.java
index c4c1bfb3e8..895d5085d9 100644
--- 
a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyEngine.java
+++ 
b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyEngine.java
@@ -106,10 +106,16 @@ public class GroovyEngine implements ScriptEngine {
     private static final String REGEX_VAR = "[a-zA-Z_]+[a-zA-Z0-9_]*";
     private static final Pattern PATTERN_FUNCTION_DEF = Pattern.compile(
             "^def\\s+(" + REGEX_VAR + ")\\s*\\(([a-zA-Z0-9_ 
,]*)\\)\\s*\\{(.*)?}(|\n)$", Pattern.DOTALL);
+    private static final Pattern PATTERN_CLASSLIKE_DEF =
+            Pattern.compile("^(class|record|interface|trait)\\s+(" + REGEX_VAR 
+ ")\\s*(.*?\\{.*?})(|\n)$", Pattern.DOTALL);
     private static final Pattern PATTERN_CLASS_DEF =
             Pattern.compile("^class\\s+(" + REGEX_VAR + 
")\\s*(.*?\\{.*?})(|\n)$", Pattern.DOTALL);
     private static final Pattern PATTERN_TRAIT_DEF =
-            Pattern.compile("^trait\\s+(" + REGEX_VAR + 
")\\s*(\\{.*?})(|\n)$", Pattern.DOTALL);
+            Pattern.compile("^trait\\s+(" + REGEX_VAR + 
")\\s*(.*?\\{.*?})(|\n)$", Pattern.DOTALL);
+    private static final Pattern PATTERN_INTERFACE_DEF =
+            Pattern.compile("^interface\\s+(" + REGEX_VAR + 
")\\s*(.*?\\{.*?})(|\n)$", Pattern.DOTALL);
+    private static final Pattern PATTERN_RECORD_DEF =
+            Pattern.compile("^record\\s+(" + REGEX_VAR + 
")\\s*(.*?\\{.*?})(|\n)$", Pattern.DOTALL);
     private static final String REGEX_CLASS = "(.*?)\\.([A-Z_].*)";
     private static final Pattern PATTERN_CLASS = Pattern.compile(REGEX_CLASS);
     private static final String REGEX_PACKAGE = "([a-z][a-z_0-9]*\\.)*";
@@ -121,6 +127,7 @@ public class GroovyEngine implements ScriptEngine {
             "java.util.*",
             "java.io.*",
             "java.net.*",
+            "java.time.*",
             "groovy.lang.*",
             "groovy.util.*",
             "java.math.BigInteger",
@@ -131,7 +138,9 @@ public class GroovyEngine implements ScriptEngine {
     private final Map<String, String> imports = new HashMap<>();
     private final Map<String, String> methods = new HashMap<>();
     private final Map<String, String> classes = new HashMap<>();
+    private final Map<String, String> interfaces = new HashMap<>();
     private final Map<String, String> traits = new HashMap<>();
+    private final Map<String, String> records = new HashMap<>();
     private final Map<String, Class<?>> nameClass;
     private Cloner objectCloner = new ObjectCloner();
     protected final EngineClassLoader classLoader;
@@ -334,23 +343,37 @@ public class GroovyEngine implements ScriptEngine {
     @Override
     public Object execute(String statement) throws Exception {
         Object out = null;
-        if (statement.matches("import\\s+(([^;\\s])+)\\s*(;)?")) {
+        if (statement.matches("import\\s+(static\\s+)?(([^;\\s])+)\\s*(;)?")) {
             String[] p = statement.split("\\s+");
-            String classname = p[1].replaceAll(";", "");
+            int classIdx = 1;
+            boolean isStatic = p[1].equals("static");
+            if (isStatic) {
+                classIdx++;
+            }
+            String classname = p[classIdx].replaceAll(";", "");
+            addToNameClass(classname);
+            if (isStatic) {
+                classname = "static " + classname;
+            }
             executeStatement(shell, imports, statement);
             imports.put(classname, statement);
-            addToNameClass(classname);
         } else if (statement.equals("import")) {
-            out = new ArrayList<>(imports.keySet());
+            List<String> allImports = new ArrayList<>(imports.keySet());
+            allImports.addAll(DEFAULT_IMPORTS);
+            out = allImports;
         } else if (functionDef(statement)) {
             // do nothing
         } else if (statement.equals("def")) {
             out = methods;
         } else if (statement.equals("class")) {
             out = classes;
-        } else if (statement.equals("traits")) {
+        } else if (statement.equals("record")) {
+            out = records;
+        } else if (statement.equals("trait")) {
             out = traits;
-        } else if (statement.matches("(def|class|trait)\\s+" + REGEX_VAR)) {
+        } else if (statement.equals("interface")) {
+            out = interfaces;
+        } else if (statement.matches("(def|class|trait|interface|record)\\s+" 
+ REGEX_VAR)) {
             String name = statement.split("\\s+")[1];
             if (statement.startsWith("def") && methods.containsKey(name)) {
                 out = "def " + name + methods.get(name);
@@ -358,6 +381,10 @@ public class GroovyEngine implements ScriptEngine {
                 out = "class " + name + " " + classes.get(name);
             } else if (statement.startsWith("trait") && 
traits.containsKey(name)) {
                 out = "trait " + name + " " + traits.get(name);
+            } else if (statement.startsWith("record") && 
records.containsKey(name)) {
+                out = "record " + name + " " + records.get(name);
+            } else if (statement.startsWith("interface") && 
interfaces.containsKey(name)) {
+                out = "interface " + name + " " + interfaces.get(name);
             }
         } else {
             out = executeStatement(shell, imports, statement);
@@ -371,6 +398,17 @@ public class GroovyEngine implements ScriptEngine {
                 Matcher matcher = PATTERN_TRAIT_DEF.matcher(statement);
                 matcher.matches();
                 traits.put(matcher.group(1), matcher.group(2));
+                addToNameClass(matcher.group(1));
+            } else if (PATTERN_INTERFACE_DEF.matcher(statement).matches()) {
+                Matcher matcher = PATTERN_INTERFACE_DEF.matcher(statement);
+                matcher.matches();
+                interfaces.put(matcher.group(1), matcher.group(2));
+                addToNameClass(matcher.group(1));
+            } else if (PATTERN_RECORD_DEF.matcher(statement).matches()) {
+                Matcher matcher = PATTERN_RECORD_DEF.matcher(statement);
+                matcher.matches();
+                records.put(matcher.group(1), matcher.group(2));
+                addToNameClass(matcher.group(1));
             }
         }
         return out;
@@ -415,7 +453,7 @@ public class GroovyEngine implements ScriptEngine {
             e.append(entry.getValue()).append("\n");
         }
         e.append(statement);
-        if (classOrTraitDef(statement)) {
+        if (classLikeDef(statement)) {
             e.append("; null");
         }
         return shell.evaluate(e.toString());
@@ -468,9 +506,8 @@ public class GroovyEngine implements ScriptEngine {
         return out;
     }
 
-    private static boolean classOrTraitDef(String statement) {
-        return PATTERN_CLASS_DEF.matcher(statement).matches()
-                || PATTERN_TRAIT_DEF.matcher(statement).matches();
+    private static boolean classLikeDef(String statement) {
+        return PATTERN_CLASSLIKE_DEF.matcher(statement).matches();
     }
 
     private void refreshNameClass() {
@@ -502,6 +539,12 @@ public class GroovyEngine implements ScriptEngine {
         } else if (classes.containsKey(var)) {
             classes.remove(var);
             classLoader.purgeClassCache(var + "(\\$.*)?");
+        } else if (interfaces.containsKey(var)) {
+            interfaces.remove(var);
+            classLoader.purgeClassCache(var + "(\\$.*)?");
+        } else if (records.containsKey(var)) {
+            records.remove(var);
+            classLoader.purgeClassCache(var + "(\\$.*)?");
         } else if (traits.containsKey(var)) {
             traits.remove(var);
             classLoader.purgeClassCache(var + "(\\$.*)?");
@@ -607,6 +650,10 @@ public class GroovyEngine implements ScriptEngine {
                 new StringsCompleter("class"), new 
StringsCompleter(classes::keySet), NullCompleter.INSTANCE));
         completers.add(new ArgumentCompleter(
                 new StringsCompleter("trait"), new 
StringsCompleter(traits::keySet), NullCompleter.INSTANCE));
+        completers.add(new ArgumentCompleter(
+                new StringsCompleter("interface"), new 
StringsCompleter(interfaces::keySet), NullCompleter.INSTANCE));
+        completers.add(new ArgumentCompleter(
+                new StringsCompleter("record"), new 
StringsCompleter(records::keySet), NullCompleter.INSTANCE));
         completers.add(new ArgumentCompleter(
                 new StringsCompleter("import"),
                 new PackageCompleter(CandidateType.PACKAGE, this),
diff --git 
a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ImportCommandTest.groovy
 
b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ImportCommandTest.groovy
deleted file mode 100644
index 10145a8455..0000000000
--- 
a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ImportCommandTest.groovy
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- *  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.groovysh.commands
-
-/**
- * Tests for the {@link ImportCommand} class.
- */
-class ImportCommandTest extends CommandTestSupport {
-    void testPatternClassOrMethodName() {
-        assert 'java.util.*'.matches(ImportCommand.IMPORTED_ITEM_PATTERN)
-        assert 'java.util.Pattern'.matches(ImportCommand.IMPORTED_ITEM_PATTERN)
-        assert 'static 
java.util.Pattern.match'.matches(ImportCommand.IMPORTED_ITEM_PATTERN)
-        assert 'java.util.Pattern as 
Fattern'.matches(ImportCommand.IMPORTED_ITEM_PATTERN)
-        assert 'java.util.Pattern'.matches(ImportCommand.IMPORTED_ITEM_PATTERN)
-        assert 
'java.util.P_attern'.matches(ImportCommand.IMPORTED_ITEM_PATTERN)
-    }
-
-    void testImport() {
-        assert null == shell << 'import'
-        assert 'java.awt.TextField' == shell << 'import java.awt.TextField'
-        // test semicolon does not lead to duplicate import
-        assert 'java.awt.TextField' == shell << 'import java.awt.TextField;'
-        // test last import is added at the end
-        assert 'java.awt.TextField, java.awt.TextArea' == shell << 'import 
java.awt.TextArea;'
-        assert 'java.awt.TextArea, java.awt.TextField' == shell << 'import 
java.awt.TextField;'
-        // test multiple commands are not allowed (as they would be executed 
on every next buffer evaluation)
-        assert null == shell << 'import java.awt.TextField; println("foo")'
-        // test *, recognizing unnecessary imports sadly not implemented
-        assert 'java.awt.TextArea, java.awt.TextField, java.awt.*' == shell << 
'import java.awt.*'
-        // test numerics being allowed in class/package names
-        assert 'java.awt.TextArea, java.awt.TextField, java.awt.*, 
org.w3c.dom.*' == shell << 'import org.w3c.dom.*'
-        assert 'java.awt.TextArea, java.awt.TextField, java.awt.*, 
org.w3c.dom.*, java.awt.Graphics2D' == shell << 'import java.awt.Graphics2D'
-    }
-}
diff --git 
a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ImportTest.groovy
 
b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ImportTest.groovy
new file mode 100644
index 0000000000..efb8d4dbb8
--- /dev/null
+++ 
b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/commands/ImportTest.groovy
@@ -0,0 +1,30 @@
+/*
+ *  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.groovysh.commands
+
+/**
+ * Tests for the {@code import} command.
+ */
+class ImportTest extends SystemTestSupport {
+    void testImport() {
+        assert system.execute('import') == []
+        system.execute('import java.awt.TextField')
+        assert system.execute('import') == ['java.awt.TextField']
+    }
+}

Reply via email to