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'] + } +}