I am using GroovyShell (2.1.7) to dynamically evaluate some Groovy code that I have stored off as a string.
GroovyShell shell = magicallyInstantiateAndBindGroovyShell();
The above method takes care of instantiating the shell, and binding all the required variables to it. Since I believe this is a syntax error, I won't clutter this question with all the variables the shell is being bound with, and what the code I'm trying to evaluate is actually doing. If it turns out that I need to add any more info to the question to help solve my problem, I'll happily oblige!
I then have a string of Groovy code that I am trying to evaluate:
com.me.myorg.myapp.ExpressionUtils.metaClass.filterMetadata = {
com.me.myorg.myapp.model.WidgetVO widget, List<String> properties ->
WidgetVO toReturn = new WidgetVO();
toReturn.setFizz(widget.getFizz());
if(widget.getBuzz().equalsIgnoreCase("BIMDER")) {
toReturn.setMode(widget.getMode());
}
for(String property : properties) {
if("some.prop".equals(property)) {
Preconditions.checkNotNull(widget.getDescriptions());
toReturn.setDescriptions(new ArrayList<DescriptionVO>());
DescriptionVO description = widget.getDescriptions().get(0);
toReturn.getDescriptions().add(description);
} else if("another.prop".equals(property)) {
Preconditions.checkNotNull(widget.getTitles().get(0));
toReturn.setTitles(new ArrayList<TitleVO>());
TitleVO title = widget.getTitles().get(0);
toReturn.getTitles().add(title);
}
}
return toReturn;
};
Which I actually have stored off as a string variable:
String code = "com.me.myorg.myapp.ExpressionUtils.metaClass.filterMetadata = { com.me.myorg.myapp.model.WidgetVO widget, List<String> properties -> WidgetVO toReturn = new WidgetVO(); toReturn.setFizz(widget.getFizz()); if(widget.getBuzz().equalsIgnoreCase(\"BIMDER\")) { toReturn.setMode(widget.getMode()); } for(String property : properties) { if(\"some.prop\".equals(property)) { Preconditions.checkNotNull(widget.getDescriptions()); toReturn.setDescriptions(new ArrayList<DescriptionVO>()); DescriptionVO description = widget.getDescriptions().get(0); toReturn.getDescriptions().add(description); } else if(\"another.prop\".equals(property)) { Preconditions.checkNotNull(widget.getTitles().get(0)); toReturn.setTitles(new ArrayList<TitleVO>()); TitleVO title = widget.getTitles().get(0); toReturn.getTitles().add(title); } } return toReturn; };
When I run:
shell.evaluate(code);
I get the following exception:
startup failed, Script1.groovy: 1: unexpected token: for # line 1, column 294.
1 error
No signature of method: com.me.myorg.myapp.ExpressionUtils.metaClass.filterMetadata() is applicable for argument types: (com.me.myorg.myapp.model.WidgetVO, java.util.ArrayList) values: {com.me.myorg.myapp.model.WidgetVO#9427908c, ["some.prop", "another.prop"]}
Column 294 is the beginning of the for-loop... but to me, this seems like perfectly fine code. Am I forgetting a closing bracket anywhere? Some other syntax error? Where am I going awry? Thanks in advance!
You have:
if(widget.getBuzz().equalsIgnoreCase(\"BIMDER\")) { toReturn.setMode(widget.getMode()); } for(String property : properties)
You need a semicolon before the for...
Why not use a multi-line string?
String code = """com.me.myorg.myapp.ExpressionUtils.metaClass.filterMetadata = { com.me.myorg.myapp.model.WidgetVO widget, List<String> properties ->
| WidgetVO toReturn = new WidgetVO()
| toReturn.setFizz(widget.getFizz())
| if( widget.getBuzz().equalsIgnoreCase( "BIMDER" ) ) {
| toReturn.setMode(widget.getMode())
| }
| for( String property : properties ) {
| if( "some.prop" == property ) {
| Preconditions.checkNotNull( widget.descriptions )
| toReturn.descriptions = [ widget.descriptions[ 0 ] ]
| }
| else if( "another.prop" == property ) {
| Preconditions.checkNotNull( widget.titles[ 0 ] )
| toReturn.titles = [ widget.titles[ 0 ] ]
| }
| }
| toReturn
|}""".stripMargin()
Related
Using Antlr 4 I have a situation I am not sure how to resolve. I originally asked the question at https://groups.google.com/forum/#!topic/antlr-discussion/1yxxxAvU678 on the Antlr discussion forum. But that forum does not seem to get a lot of traffic, so I am asking again here.
I have the following grammar:
expression
: ...
| path
;
path
: ...
| dotIdentifierSequence
;
dotIdentifierSequence
: identifier (DOT identifier)*
;
The concern here is that dotIdentifierSequence can mean a number of things semantically, and not all of them are "paths". But at the moment they are all recognized as paths in the parse tree and then I need to handle them specially in my visitor.
But what I'd really like is a way to express the dotIdentifierSequence usages that are not paths into the expression rule rather than in the path rule, and still have dotIdentifierSequence in path to handle path usages.
To be clear, a dotIdentifierSequence might be any of the following:
A path - this is a SQL-like grammar and a path expression would be like a table or column reference in SQL, e.g. a.b.c
A Java class name - e.g. com.acme.SomeJavaType
A static Java field reference - e.g. com.acme.SomeJavaType.SOME_FIELD
A Java enum value reference - e.g. com.acme.Gender.MALE
The idea is that during visitation "dotIdentifierSequence as a path" resolves as a very different type from the other usages.
Any idea how I can do this?
The issue here is that you're trying to make a distinction between "paths" while being created in the parser. Constructing paths inside the lexer would be easier (pseudo code follows):
grammar T;
tokens {
JAVA_TYPE_PATH,
JAVA_FIELD_PATH
}
// parser rules
PATH
: IDENTIFIER ('.' IDENTIFIER)*
{
String s = getText();
if (s is a Java class) {
setType(JAVA_TYPE_PATH);
} else if (s is a Java field) {
setType(JAVA_FIELD_PATH);
}
}
;
fragment IDENTIFIER : [a-zA-Z_] [a-zA-Z_0-9]*;
and then in the parser you would do:
expression
: JAVA_TYPE_PATH #javaTypeExpression
| JAVA_FIELD_PATH #javaFieldExpression
| PATH #pathExpression
;
But then, of course, input like this java./*comment*/lang.String would be tokenized wrongly.
Handling it all in the parser would mean manually looking ahead in the token stream and checking if either a Java type, or field exists.
A quick demo:
grammar T;
#parser::members {
String getPathAhead() {
Token token = _input.LT(1);
if (token.getType() != IDENTIFIER) {
return null;
}
StringBuilder builder = new StringBuilder(token.getText());
// Try to collect ('.' IDENTIFIER)*
for (int stepsAhead = 2; ; stepsAhead += 2) {
Token expectedDot = _input.LT(stepsAhead);
Token expectedIdentifier = _input.LT(stepsAhead + 1);
if (expectedDot.getType() != DOT || expectedIdentifier.getType() != IDENTIFIER) {
break;
}
builder.append('.').append(expectedIdentifier.getText());
}
return builder.toString();
}
boolean javaTypeAhead() {
String path = getPathAhead();
if (path == null) {
return false;
}
try {
return Class.forName(path) != null;
} catch (Exception e) {
return false;
}
}
boolean javaFieldAhead() {
String path = getPathAhead();
if (path == null || !path.contains(".")) {
return false;
}
int lastDot = path.lastIndexOf('.');
String typeName = path.substring(0, lastDot);
String fieldName = path.substring(lastDot + 1);
try {
Class<?> clazz = Class.forName(typeName);
return clazz.getField(fieldName) != null;
} catch (Exception e) {
return false;
}
}
}
expression
: {javaTypeAhead()}? path #javaTypeExpression
| {javaFieldAhead()}? path #javaFieldExpression
| path #pathExpression
;
path
: dotIdentifierSequence
;
dotIdentifierSequence
: IDENTIFIER (DOT IDENTIFIER)*
;
IDENTIFIER
: [a-zA-Z_] [a-zA-Z_0-9]*
;
DOT
: '.'
;
which can be tested with the following class:
package tl.antlr4;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
public class Main {
public static void main(String[] args) {
String[] tests = {
"mu",
"tl.antlr4.The",
"java.lang.String",
"foo.bar.Baz",
"tl.antlr4.The.answer",
"tl.antlr4.The.ANSWER"
};
for (String test : tests) {
TLexer lexer = new TLexer(new ANTLRInputStream(test));
TParser parser = new TParser(new CommonTokenStream(lexer));
ParseTreeWalker.DEFAULT.walk(new TestListener(), parser.expression());
}
}
}
class TestListener extends TBaseListener {
#Override
public void enterJavaTypeExpression(#NotNull TParser.JavaTypeExpressionContext ctx) {
System.out.println("JavaTypeExpression -> " + ctx.getText());
}
#Override
public void enterJavaFieldExpression(#NotNull TParser.JavaFieldExpressionContext ctx) {
System.out.println("JavaFieldExpression -> " + ctx.getText());
}
#Override
public void enterPathExpression(#NotNull TParser.PathExpressionContext ctx) {
System.out.println("PathExpression -> " + ctx.getText());
}
}
class The {
public static final int ANSWER = 42;
}
which would print the following to the console:
PathExpression -> mu
JavaTypeExpression -> tl.antlr4.The
JavaTypeExpression -> java.lang.String
PathExpression -> foo.bar.Baz
PathExpression -> tl.antlr4.The.answer
JavaFieldExpression -> tl.antlr4.The.ANSWER
I have class in groovy
class WhsDBFile {
String name
String path
String svnUrl
String lastRevision
String lastMessage
String lastAuthor
}
and map object
def installFiles = [:]
that filled in loop by
WhsDBFile dbFile = new WhsDBFile()
installFiles[svnDiffStatus.getPath()] = dbFile
now i try to sort this with custom Comparator
Comparator<WhsDBFile> whsDBFileComparator = new Comparator<WhsDBFile>() {
#Override
int compare(WhsDBFile o1, WhsDBFile o2) {
if (FilenameUtils.getBaseName(o1.name) > FilenameUtils.getBaseName(o2.name)) {
return 1
} else if (FilenameUtils.getBaseName(o1.name) > FilenameUtils.getBaseName(o2.name)) {
return -1
}
return 0
}
}
installFiles.sort(whsDBFileComparator);
but get this error java.lang.String cannot be cast to WhsDBFile
Any idea how to fix this? I need to use custom comparator, cause it will be much more complex in the future.
p.s. full source of sample gradle task (description of WhsDBFile class is above):
project.task('sample') << {
def installFiles = [:]
WhsDBFile dbFile = new WhsDBFile()
installFiles['sample_path'] = dbFile
Comparator<WhsDBFile> whsDBFileComparator = new Comparator<WhsDBFile>() {
#Override
int compare(WhsDBFile o1, WhsDBFile o2) {
if (o1.name > o2.name) {
return 1
} else if (o1.name > o2.name) {
return -1
}
return 0
}
}
installFiles.sort(whsDBFileComparator);
}
You can try to sort the entrySet() :
def sortedEntries = installFiles.entrySet().sort { entry1, entry2 ->
entry1.value <=> entry2.value
}
you will have a collection of Map.Entry with this invocation. In order to have a map, you can then collectEntries() the result :
def sortedMap = installFiles.entrySet().sort { entry1, entry2 ->
...
}.collectEntries()
sort can also take a closure as parameter which coerces to a Comparator's compare() method as below. Usage of toUpper() method just mimics the implementation of FilenameUtils.getBaseName().
installFiles.sort { a, b ->
toUpper(a.value.name) <=> toUpper(b.value.name)
}
// Replicating implementation of FilenameUtils.getBaseName()
// This can be customized according to requirement
String toUpper(String a) {
a.toUpperCase()
}
I am getting the following error -
groovy.lang.MissingMethodException: No signature of method: Script64$_run_closure5_closure7_closure8_closure9_closure10_closure11.doCall() is applicable for argument types: (java.lang.String) values: Possible solutions: doCall(java.lang.Object, java.lang.Object), isCase(java.lang.Object), isCase(java.lang.Object) error at line:
Code - EDIT
import groovy.xml.*
List tempList = []
List listgenerated = []
def count = 0
for (a in 0..totalCount-1)
{
//nameList and valueList lists will have all the contents added as below commented pseudo code
/*for (b in 0..50)
{
nameList.add(b,number) // number is some calculated value
valueList.add(b,number)
e.g. nameList=[name1, name2, name3,name4, name5]
valueList =[val1, val2, val3, , val5]
listgenerated should be = [[name1:val1, name2:val2], [name3:val3, name4: , name5:val5]]
} */
tempList = []
for (j in count..nameList.size())
{
count = j
def nameKey = nameList[j]
def value
if (nameKey != null)
{
value = valueList[j]
tempList << [(nameKey) : value]
}
}
count = count
listgenerated.putAt(a,tempList)
number = number +1
}
def process = { binding, element, name ->
if( element[ name ] instanceof Collection ) {
element[ name ].each { n ->
binding."$name"( n )
}
}
else if( element[ name ] ) {
binding."$name"( element[ name ] )
}
}
class Form {
List fields
}
def list = [[ name:'a', val:'1' ], [ name:'b', val :'2', name2:4, xyz:'abc', pqr:'']] //Edited list
f = new Form( fields: list ) //Works fine
f = new Form( fields: listgenerated ) //Gives the above error
String xml = XmlUtil.serialize( new StreamingMarkupBuilder().with { builder ->
builder.bind { binding ->
data {
f.fields.each { fields ->
item {
fields.each { name, value ->
process( binding, fields, name )
}
}
}
}
}
} )
If while creating the "listgenerated" single quotes are added around values it takes it as character and while printing both lists seem different.
I am unable to figure it out what exactly is going wrong. Any help is appreciated. Thanks.
Ref - Groovy: dynamically create XML for collection of objects with collections of properties
I believe, where you do:
//some loop to add multiple values to the list
listgenerated << name+":"+value
You need to do:
//some loop to add multiple values to the list
listgenerated << [ (name): value ]
And add a map to the list rather than a String. It's hard to say though as your code example doesn't run without alteration, and I don't know if it's the alterations that are solving the problem
I want to use either a value of expected property or a specified default.
How to achieve this in groovy?
Let's look at the example:
def printName(object) {
//if object has initialized property 'name' - print 'name', otherwise print ToString
if (object<some code here>name && object.name) {
print object.name
} else {
print object
}
}
You can use hasProperty. Example:
if (object.hasProperty('name') && object.name) {
println object.name
} else {
println object
}
If you're using a variable for the property name, you can use this:
String propName = 'name'
if (object.hasProperty(propName) && object."$propName") {
...
}
Assuming your object is a Groovy class, you can use hasProperty in the object metaClass like so:
def printName( o ) {
if( o.metaClass.hasProperty( o, 'name' ) && o.name ) {
println "Printing Name : $o.name"
}
else {
println o
}
}
So, then given two classes:
class Named {
String name
int age
String toString() { "toString Named:$name/$age" }
}
class Unnamed {
int age
String toString() { "toString Unnamed:$age" }
}
You can create instance of them, and test:
def a = new Named( name: 'tim', age: 21 )
def b = new Unnamed( age: 32 )
printName( a )
printName( b )
Which should output:
Printing Name : tim
toString Unnamed:32
You can write your own method via meta-programming:
class Foo {
def name = "Mozart"
}
def f = new Foo()
Object.metaClass.getPropertyOrElse = { prop, defaultVal ->
delegate.hasProperty(prop) ? delegate."${prop}" : defaultVal
}
assert "Mozart" == f.getPropertyOrElse("name", "")
assert "Salzburg" == f.getPropertyOrElse("city", "Salzburg")
If I simply want to assert that an object has some property, I just test the following:
assertNotNull(myObject.hasProperty('myProperty').name)
If myObject does not have myProperty the assertion will fail with a null pointer exception:
java.lang.NullPointerException: Cannot get property 'name' on null object
I have the following code which copies property values from one object to another objects by matching their property names:
public static void CopyProperties(object source, object target,bool caseSenstive=true)
{
PropertyInfo[] targetProperties = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo[] sourceProperties = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo tp in targetProperties)
{
var sourceProperty = sourceProperties.FirstOrDefault(p => p.Name == tp.Name);
if (sourceProperty == null && !caseSenstive)
{
sourceProperty = sourceProperties.FirstOrDefault(p => p.Name.ToUpper() == tp.Name.ToUpper());
}
// If source doesn't have this property, go for next one.
if(sourceProperty ==null)
{
continue;
}
// If target property is not writable then we can not set it;
// If source property is not readable then cannot check it's value
if (!tp.CanWrite || !sourceProperty.CanRead)
{
continue;
}
MethodInfo mget = sourceProperty.GetGetMethod(false);
MethodInfo mset = tp.GetSetMethod(false);
// Get and set methods have to be public
if (mget == null)
{
continue;
}
if (mset == null)
{
continue;
}
var sourcevalue = sourceProperty.GetValue(source, null);
tp.SetValue(target, sourcevalue, null);
}
}
This is working well when the type of properties on target and source are the same. But when there is a need for casting, the code doesn't work.
For example, I have the following object:
class MyDateTime
{
public static implicit operator DateTime?(MyDateTime myDateTime)
{
return myDateTime.DateTime;
}
public static implicit operator DateTime(MyDateTime myDateTime)
{
if (myDateTime.DateTime.HasValue)
{
return myDateTime.DateTime.Value;
}
else
{
return System.DateTime.MinValue;
}
}
public static implicit operator MyDateTime(DateTime? dateTime)
{
return FromDateTime(dateTime);
}
public static implicit operator MyDateTime(DateTime dateTime)
{
return FromDateTime(dateTime);
}
}
If I do the following, the implicit cast is called and everything works well:
MyDateTime x= DateTime.Now;
But when I have a two objects that one of them has a DateTime and the other has MyDateTime, and I am using the above code to copy properties from one object to other, it doesn't and generate an error saying that DateTime can not converted to MyTimeDate.
How can I fix this problem?
One ghastly approach which should work is to mix dynamic and reflection:
private static T ConvertValue<T>(dynamic value)
{
return value; // This will perform conversion automatically
}
Then:
var sourceValue = sourceProperty.GetValue(source, null);
if (sourceProperty.PropertyType != tp.PropertyType)
{
var method = typeof(PropertyCopier).GetMethod("ConvertValue",
BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(new[] { tp.PropertyType };
sourceValue = method.Invoke(null, new[] { sourceValue });
}
tp.SetValue(target, sourceValue, null);
We need to use reflection to invoke the generic method with the right type argument, but dynamic typing will use the right conversion operator for you.
Oh, and one final request: please don't include my name anywhere near this code, whether it's in comments, commit logs. Aargh.