Skip to content

Commit

Permalink
Groovy REPL does not load classes from groovy sources, fixes #664
Browse files Browse the repository at this point in the history
  • Loading branch information
mattirn committed Apr 2, 2021
1 parent eb1199e commit 6ffcadf
Showing 1 changed file with 101 additions and 22 deletions.
123 changes: 101 additions & 22 deletions groovy/src/main/java/org/jline/script/GroovyEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@
*/
package org.jline.script;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.nio.file.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -25,6 +22,7 @@
import groovy.lang.*;
import org.apache.groovy.ast.tools.ImmutablePropertyUtils;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack;
import org.codehaus.groovy.syntax.SyntaxException;
import org.jline.builtins.Nano.SyntaxHighlighter;
import org.jline.builtins.Styles;
Expand Down Expand Up @@ -79,6 +77,7 @@ public enum Format {JSON, GROOVY, NONE}
private static final Pattern PATTERN_CLASS_DEF = Pattern.compile("^class\\s+(" + REGEX_VAR + ") .*?\\{.*?}(|\n)$"
, Pattern.DOTALL);
private static final Pattern PATTERN_CLASS_NAME = Pattern.compile("(.*?)\\.([A-Z].*)");
private static final Pattern PATTERN_LOAD_CLASS = Pattern.compile("(new\\s+)*\\s*(([a-z]+\\.)*)([A-Z]+[a-zA-Z]*)+(\\..*|\\(.*|)");
private static final List<String> DEFAULT_IMPORTS = Arrays.asList("java.lang.*", "java.util.*", "java.io.*"
, "java.net.*", "groovy.lang.*", "groovy.util.*"
, "java.math.BigInteger", "java.math.BigDecimal");
Expand Down Expand Up @@ -267,17 +266,48 @@ public Object execute(String statement) throws Exception {
out = "def " + name + methods.get(name);
}
} else {
StringBuilder e = new StringBuilder();
out = executeStatement(shell, imports, statement);
}
return out;
}

private static Object executeStatement(GroovyShell shell, Map<String, String> imports, String statement) throws IOException {
boolean classLoaded = false;
int idx = statement.indexOf("=") + 1;
Matcher matcher = PATTERN_LOAD_CLASS.matcher(statement.substring(idx));
if (matcher.matches()) {
String fileName = convertNull(matcher.group(2)) + matcher.group(4);
fileName = fileName.replace(".", "/");
for (String type : Arrays.asList(".groovy", ".java")) {
File file = new File(fileName + type);
if (file.exists()) {
try {
shell.evaluate(file);
} catch (MissingMethodExceptionNoStack ignore) {

}
classLoaded = true;
statement = statement.substring(0, idx) + convertNull(matcher.group(1)) + matcher.group(4)
+ convertNull(matcher.group(5));
break;
}
}
}
StringBuilder e = new StringBuilder();
if (!classLoaded) {
for (Map.Entry<String, String> entry : imports.entrySet()) {
e.append(entry.getValue()).append("\n");
}
e.append(statement);
if (classDef(statement)) {
e.append("; null");
}
out = shell.evaluate(e.toString());
}
return out;
e.append(statement);
if (classDef(statement)) {
e.append("; null");
}
return shell.evaluate(e.toString());
}

private static String convertNull(String string) {
return string == null ? "" : string;
}

@Override
Expand Down Expand Up @@ -323,7 +353,7 @@ private boolean functionDef(String statement) throws Exception{
return out;
}

private boolean classDef(String statement) {
private static boolean classDef(String statement) {
return PATTERN_CLASS_DEF.matcher(statement).matches();
}

Expand Down Expand Up @@ -552,6 +582,41 @@ private static Map<String,String> getFields(Class<?> clazz, boolean all, boolean
return out;
}

private static Set<String> fileDomain() {
return nextFileDomain(null, 0);
}

private static Set<String> nextFileDomain(String domain, int position) {
String separator = FileSystems.getDefault().getSeparator();
if (separator.equals("\\")) {
separator += separator;
}
String dom;
if (domain != null) {
dom = domain.isEmpty() ? ".*" + separator : separator + domain.replace(".", separator);
} else {
dom = separator;
}
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("regex:\\." + dom
+ "[A-Z]+[a-zA-Z]*\\.(groovy|java)");
Set<String> out = new HashSet<>();
try {
List<Path> paths = Files.walk(Paths.get(".")).filter(matcher::matches).collect(Collectors.toList());
for (Path p : paths) {
if(!p.getFileName().toString().matches("[A-Z]+[a-zA-Z]*\\.(groovy|java)")){
continue;
}
String[] s = p.toString().split(separator);
if (s.length > position + 1) {
out.add(s[position + 1].split(".(groovy|java)")[0]);
}
}
} catch(Exception ignore) {

}
return out;
}

public static Set<String> nextDomain(String domain, CandidateType type) {
return nextDomain(domain, new AccessRules(), type);
}
Expand All @@ -562,8 +627,14 @@ public static Set<String> nextDomain(String domain, AccessRules access, Candidat
for (String p : loadedPackages()) {
out.add(p.split("\\.")[0]);
}
if (type != CandidateType.PACKAGE) {
out.addAll(nextFileDomain("", 0));
}
} else if ((domain.split("\\.")).length < 2) {
out = names(domain);
if (type != CandidateType.PACKAGE) {
out.addAll(nextFileDomain(domain, 1));
}
} else {
try {
for (Class<?> c : classesForPackage(domain)) {
Expand Down Expand Up @@ -592,6 +663,9 @@ && noStaticFields(c, access.allFields))) {
}
}
}
if (out.isEmpty() && type != CandidateType.PACKAGE) {
out.addAll(nextFileDomain(domain, domain.split("\\.").length));
}
} catch (ClassNotFoundException e) {
if (Log.isDebugEnabled()) {
e.printStackTrace();
Expand Down Expand Up @@ -853,6 +927,9 @@ public void complete(LineReader reader, ParsedLine commandLine, List<Candidate>
try {
param = wordbuffer.substring(eqsep + 1, varsep);
Class<?> clazz = classResolver(param);
if (clazz == null) {
clazz = (Class<?>)inspector.execute(param + ".class");
}
if (clazz != null) {
doStaticMethodCandidates(candidates, clazz, curBuf);
}
Expand Down Expand Up @@ -991,6 +1068,7 @@ private Set<String> retrieveConstructors(boolean all) {
it.remove();
}
}
out.addAll(Helpers.fileDomain());
return out;
}

Expand All @@ -1008,6 +1086,7 @@ private Set<String> retrieveClassesWithStaticMethods() {
it.remove();
}
}
out.addAll(Helpers.fileDomain());
return out;
}
}
Expand Down Expand Up @@ -1091,7 +1170,11 @@ public Class<?> evaluateClass(String objectStatement) {
if (!objectStatement.contains(".") ) {
out = (Class<?>)execute(objectStatement + ".class");
} else {
out = Class.forName(objectStatement);
try {
out = Class.forName(objectStatement);
} catch (ClassNotFoundException e) {
out = (Class<?>)execute(objectStatement + ".class");
}
}
}
} catch (Exception e) {
Expand All @@ -1107,14 +1190,10 @@ public Object execute(String statement) {
System.setOut(nullstream);
System.setErr(nullstream);
}
Object out;
Object out = null;
try {
StringBuilder e = new StringBuilder();
for (Map.Entry<String, String> entry : imports.entrySet()) {
e.append(entry.getValue()).append("\n");
}
e.append(statement);
out = shell.evaluate(e.toString());
out = executeStatement(shell, imports, statement);
} catch (IOException ignore) {
} finally {
System.setOut(origOut);
System.setErr(origErr);
Expand Down Expand Up @@ -1481,7 +1560,7 @@ private CmdDesc checkSyntax(CmdLine line) {
} else {
try {
execute(objEquation);
} catch (groovy.lang.MissingPropertyException e) {
} catch (MissingPropertyException e) {
mainDesc.addAll(doExceptionMessage(e));
out.setErrorPattern(Pattern.compile("\\b" + e.getProperty() + "\\b"));
} catch (java.util.regex.PatternSyntaxException e) {
Expand Down

2 comments on commit 6ffcadf

@MykolaGolubyev
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome! Thank you.
Does it also handles import and new by any chance?

@mattirn
Copy link
Collaborator Author

@mattirn mattirn commented on 6ffcadf Apr 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not handle import, new yes as well as statement completion.

Please sign in to comment.