Skip to content

Commit

Permalink
Merge pull request jenkinsci#70 from abayer/jenkins-46358
Browse files Browse the repository at this point in the history
[FIXED JENKINS-46358] Add support for StringGroovyMethods w/closures
  • Loading branch information
abayer committed Aug 25, 2017
2 parents d54073c + d93226e commit 0b097ce
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ public void run(File dir) throws Exception {

// classes to translate
// TODO include other classes mentioned in DgmConverter if they have any applicable methods; and certainly StringGroovyMethods which has some Closure-based methods
List<String> fileNames = asList("DefaultGroovyMethods", "DefaultGroovyStaticMethods");
List<String> fileNames = asList("DefaultGroovyMethods",
"DefaultGroovyStaticMethods",
"StringGroovyMethods");

List<JavaFileObject> src = new ArrayList<>();
ZipArchive a = new ZipArchive((JavacFileManager) fileManager, new ZipFile(groovySrcJar));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.annotation.Generated;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
Expand Down Expand Up @@ -129,6 +128,7 @@ public class Translator {
private final JClass $Builder;
private final JClass $CatchExpression;
private final DeclaredType closureType;
private final Map<String,JClass> otherTranslated = new HashMap<>();

/**
* To allow sibling calls to overloads to be resolved properly at runtime, write the actual implementation to an overload-proof private method.
Expand Down Expand Up @@ -214,6 +214,9 @@ private static MethodLocation loc(String methodName) {
JVar $methodName = m.param(String.class, "methodName");
m.body()._return(JExpr._new($MethodLocation).arg($output.dotclass()).arg($methodName));
});

// Record the fqcn we've already translated for possible use later.
otherTranslated.put(fqcn, $output);
}

/**
Expand Down Expand Up @@ -333,20 +336,43 @@ public JExpression visitMethodInvocation(MethodInvocationTree mt, Void __) {

if (ms instanceof MemberSelectTree) {
MemberSelectTree mst = (MemberSelectTree) ms;
inv = $b.invoke("functionCall")
.arg(loc(mt))
.arg(visit(mst.getExpression()))
.arg(n(mst.getIdentifier()));
// If this is a call to a static method on another class, it may be an already-translated method,
// in which case, we need to use that translated method, not the original. So check if the expression
// is an identifier, that it's not the class we're in the process of translating, and if it's one
// of the other known translated classes.
if (mst.getExpression() instanceof JCIdent &&
!((JCIdent)mst.getExpression()).sym.toString().equals(fqcn) &&
otherTranslated.containsKey(((JCIdent)mst.getExpression()).sym.toString())) {
inv = $b.invoke("functionCall")
.arg(loc(mt))
.arg($b.invoke("constant").arg(
otherTranslated.get(((JCIdent)mst.getExpression()).sym.toString()).dotclass()))
.arg(n(mst.getIdentifier()));

} else {
inv = $b.invoke("functionCall")
.arg(loc(mt))
.arg(visit(mst.getExpression()))
.arg(n(mst.getIdentifier()));
}
} else
if (ms instanceof JCIdent) {
// invocation without object selection, like foo(bar,zot)
JCIdent it = (JCIdent) ms;
if (!it.sym.owner.toString().equals(fqcn)) {
// static import
inv = $b.invoke("functionCall")
.arg(loc(mt))
.arg($b.invoke("constant").arg(t(it.sym.owner.type).dotclass()))
.arg(n(it));
if (otherTranslated.containsKey(it.sym.owner.toString())) {
// static import from transformed class
inv = $b.invoke("functionCall")
.arg(loc(mt))
.arg($b.invoke("constant").arg(otherTranslated.get(it.sym.owner.toString()).dotclass()))
.arg(n(it));
} else {
// static import from non-transformed class
inv = $b.invoke("functionCall")
.arg(loc(mt))
.arg($b.invoke("constant").arg(t(it.sym.owner.type).dotclass()))
.arg(n(it));
}
} else {
// invocation on this class
String overloadResolved = mangledName((Symbol.MethodSymbol) it.sym);
Expand Down Expand Up @@ -537,6 +563,18 @@ public JExpression visitAssignment(AssignmentTree at, Void __) {
.arg(visit(at.getExpression()));
}

/**
* This is needed to handle cases like {@code Object[].class}.
*/
@Override
public JExpression visitArrayType(ArrayTypeTree at, Void __) {
if (at.getType() instanceof IdentifierTree) {
return visitIdentifier((IdentifierTree) at.getType(), __);
} else {
return defaultAction(at, __);
}
}

@Override
public JExpression visitNewArray(NewArrayTree nt, Void __) {
if (nt.getInitializers()!=null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,26 @@ org.codehaus.groovy.runtime.DefaultGroovyMethods.upto(long,java.lang.Number,groo
org.codehaus.groovy.runtime.DefaultGroovyStaticMethods.createThread(java.lang.String,boolean,groovy.lang.Closure)
org.codehaus.groovy.runtime.DefaultGroovyStaticMethods.sleep(java.lang.Object,long,groovy.lang.Closure)
org.codehaus.groovy.runtime.DefaultGroovyStaticMethods.sleepImpl(long,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.<T>eachMatch(T,java.lang.CharSequence,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.<T>eachMatch(T,java.util.regex.Pattern,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.eachMatch(java.lang.String,java.util.regex.Pattern,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.eachMatch(java.lang.String,java.lang.String,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.find(java.lang.CharSequence,java.lang.CharSequence,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.find(java.lang.CharSequence,java.util.regex.Pattern,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.find(java.lang.String,java.util.regex.Pattern,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.find(java.lang.String,java.lang.String,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.<T>findAll(java.lang.CharSequence,java.lang.CharSequence,groovy.lang.Closure<T>)
org.codehaus.groovy.runtime.StringGroovyMethods.<T>findAll(java.lang.CharSequence,java.util.regex.Pattern,groovy.lang.Closure<T>)
org.codehaus.groovy.runtime.StringGroovyMethods.<T>findAll(java.lang.String,java.util.regex.Pattern,groovy.lang.Closure<T>)
org.codehaus.groovy.runtime.StringGroovyMethods.<T>findAll(java.lang.String,java.lang.String,groovy.lang.Closure<T>)
org.codehaus.groovy.runtime.StringGroovyMethods.getReplacement(java.util.regex.Matcher,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.replaceAll(java.lang.CharSequence,java.lang.CharSequence,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.replaceAll(java.lang.CharSequence,java.util.regex.Pattern,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.replaceAll(java.lang.String,java.util.regex.Pattern,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.replaceAll(java.lang.String,java.lang.String,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.replaceFirst(java.lang.CharSequence,java.lang.CharSequence,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.replaceFirst(java.lang.CharSequence,java.util.regex.Pattern,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.replaceFirst(java.lang.String,java.util.regex.Pattern,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.replaceFirst(java.lang.String,java.lang.String,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.takeWhile(java.lang.CharSequence,groovy.lang.Closure)
org.codehaus.groovy.runtime.StringGroovyMethods.takeWhile(groovy.lang.GString,groovy.lang.Closure)
3 changes: 2 additions & 1 deletion lib/src/main/java/com/cloudbees/groovy/cps/Continuable.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public class Continuable implements Serializable {
@SuppressWarnings("rawtypes")
public static final List<Class> categories = ImmutableList.<Class>of(
CpsDefaultGroovyMethods.class,
CpsDefaultGroovyStaticMethods.class);
CpsDefaultGroovyStaticMethods.class,
CpsStringGroovyMethods.class);

/**
* When the program resumes with a value (in particular an exception thrown), what environment
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.cloudbees.groovy.cps

import org.junit.Ignore
import org.junit.Test


class CpsStringGroovyMethodsTest extends AbstractGroovyCpsTest {
@Test
void eachMatch() {
evalCPSAndSync('''
int matchCount = 0
"foobarfoooobar".eachMatch(~/foo/) { matchCount++ }
return matchCount
''', 2)

evalCPSAndSync('''
int matchCount = 0
"foobarfoooobar".eachMatch('foo') { matchCount++ }
return matchCount
''', 2)
}

@Test
void find() {
evalCPSAndSync('''
return "foobar".find("oob") { it.reverse() }
''', "raboof")

evalCPSAndSync('''
return "foobar".find(~/oob/) { it.reverse() }
''', "raboof")
}

@Test
void findAll() {
evalCPSAndSync('''
return "foobarfoobarfoo".findAll("foo") { it.reverse() }
''', ['oof', 'oof', 'oof'])

evalCPSAndSync('''
return "foobarfoobarfoo".findAll(~/foo/) { it.reverse() }
''', ['oof', 'oof', 'oof'])
}

@Test
void replaceAll() {
evalCPSAndSync('''
return "foobarfoobarfoo".replaceAll("foo") { it.reverse() }
''', "oofbaroofbaroof")

evalCPSAndSync('''
return "foobarfoobarfoo".replaceAll(~/foo/) { it.reverse() }
''', "oofbaroofbaroof")
}

@Test
void replaceFirst() {
evalCPSAndSync('''
return "foobarfoobarfoo".replaceFirst("foo") { it.reverse() }
''', "oofbarfoobarfoo")

evalCPSAndSync('''
return "foobarfoobarfoo".replaceFirst(~/foo/) { it.reverse() }
''', "oofbarfoobarfoo")
}

@Ignore("Waiting for StringGroovyMethods.LineIterable translation")
@Test
void splitEachLine() {
evalCPSAndSync('''
return """
abc|def
ghi|jkl
mno|pqr
""".splitEachLine("|") { it.reverse() }
''', "bob")
}

@Test
void takeWhile() {
evalCPSAndSync('''
return "Groovy".takeWhile{ it != 'v' }
''', "Groo")

evalCPSAndSync('''
def ovyStr = 'ovy'
return "Gro${ovyStr}".takeWhile{ it != "v" }
''', "Groo")
}

private void evalCPSAndSync(String script, Object value) {
evalCPS(script) == value
evalCPS("""
@NonCPS
def someMethod() {
${script}
}
someMethod()
""") == value
}
}

0 comments on commit 0b097ce

Please sign in to comment.