简体   繁体   中英

Create dynamically closures in Groovy from a String object

i would like to create a query with the Criteria API in Grails (GORM). The query will have to be something like this:

MyEntity.createCriteria().list{
   assoc{
      parent{
         eq("code", val)
      }
   }
}

What i need is to build the nested closure dynamically from a String object. The String for the example above will be "assoc.parent.code" . I splitted the String by dot (by doing String.split("\\\\.") ) but i don't know how to construct the nested closures:

   assoc{
      parent{
         eq("code", val)
      }
   }

dynamically based on the array of the splitted Strings above.

What about createAlias ?. You could try something like this:

def path = "assoc.parent.code"

def split = path.split(/\./)

MyEntity.createCriteria().list {
  // this will get you 'createAlias( assoc.parent, alias1 )'
  createAlias split.take( split.size() - 1 ), "alias1"

  // this will get you 'eq(alias1.code, userInput)'
  eq "alias1.${split[-1]}", userInput
}

This snippet is not generic, but you get the idea.


Update

Not conventional, but you can build a string containing the code with the closures and evaluate it using GroovyShell :

assoc = 'assoc.parent.child.name'
split = assoc.split( /\./ )
path  = split[-2..0] // will get us 'child.parent.assoc';
                     // we will build it from inside-out

def firstClosure = "{ eq '${split[-1]}', 'john doe' }"
def lastClosure = firstClosure

for (entity in path) {
  def criteriaClosure =  "{ ${entity} ${lastClosure} }"
  lastClosure = criteriaClosure
}

assert lastClosure == "{ assoc { parent { child { eq 'name', 'john doe' } } } }"
def builtClosure = new GroovyShell().evaluate("return " + lastClosure)
assert builtClosure instanceof Closure

A more generic approach would be to metaClass String as below, and use it for any kind of separator
. | , - ~ . | , - ~ and more.

String.metaClass.convertToClosureWithValue = {op, val ->
    split = delegate.split(op) as List
    if(split.size() == 1) {return "Cannot split string '$delegate' on '$op'"} 

    items = []
    split.each{
        if(it == split.last()){
            items << "{ eq '$it', $val }"
            split.indexOf(it).times{items.push("}")}
        } else {
            items << "{$it"
        }
    }

    println items.join()
    new GroovyShell().evaluate("return " + items.join())
}

assert "assoc.parent.child.name".convertToClosureWithValue(/\./, "John Doe") instanceof Closure
assert "assoc-parent-child-name".convertToClosureWithValue(/\-/, "Billy Bob") instanceof Closure
assert "assoc|parent|child|grandChild|name".convertToClosureWithValue(/\|/, "Max Payne") instanceof Closure
assert "assoc~parent~child~grandChild~name".convertToClosureWithValue('\\~', "Private Ryan") instanceof Closure
assert "assocparentchildname".convertToClosureWithValue(/\|/, "Captain Miller") == "Cannot split string 'assocparentchildname' on '\\|'"

//Print lines from items.join()
{assoc{parent{child{ eq 'name', John Doe }}}}
{assoc{parent{child{ eq 'name', Billy Bob }}}}
{assoc{parent{child{grandChild{ eq 'name', Max Payne }}}}}
{assoc{parent{child{grandChild{ eq 'name', Private Ryan }}}}}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM