Xtext custom validator warning - How to mark keyword? - validation

I have the simple Greeting example in xtext. So the DSL is defined like this:
grammar org.xtext.example.mydsl.Tests with org.eclipse.xtext.common.Terminals
generate tests "http://www.xtext.org/example/mydsl/Tests"
Model:
greetings+= Greeting*;
Greeting:
'Hello' name=ID '!';
Moreover, I have the following validator:
#Check
def checkGreetingStartsWithCapital(Greeting greeting) {
if (!Character.isUpperCase(greeting.name.charAt(0))) {
warning('Name should start with a capital',
TestsPackage.Literals.GREETING__NAME,
-1,
INVALID_NAME)
}
}
If I write the validator like this and have an expression like
"Hello world!"
in my model, the "world" is marked, i.e. there is this yellow line under it. What do I have to do if I want to mark only the keyword so in this case only the "Hello"?
I tried quite a few things and I can only manage to either mark the whole line "Hello world!" or just the "world".
Thank you!

have a look at the other methods for reporting a warning/error. there is one that takes an offset and length. you can use the node model to get them for the keyword
class MyDslValidator extends AbstractMyDslValidator {
public static val INVALID_NAME = 'invalidName'
#Inject extension MyDslGrammarAccess
#Check
def checkGreetingStartsWithCapital(Greeting greeting) {
if (!Character.isUpperCase(greeting.name.charAt(0))) {
val node = NodeModelUtils.findActualNodeFor(greeting)
for (n : node.asTreeIterable) {
val ge = n.grammarElement
if (ge instanceof Keyword && ge == greetingAccess.helloKeyword_0) {
messageAcceptor.acceptWarning(
'Name should start with a capital',
greeting,
n.offset,
n.length,
INVALID_NAME
)
}
}
}
}
}

I found another very simple solution I haven't thought of that includes changing the DSL a bit, i.e. add the keyword as a attribute.
Greeting:
keyword='Hello' name=ID '!';
Then the validator works as in the question:
#Check
def checkGreetingStartsWithCapital(Greeting greeting) {
if (!Character.isUpperCase(greeting.name.charAt(0))) {
warning('Name should start with a capital',
TestsPackage.Literals.GREETING__KEYWORD,
-1,
INVALID_NAME)
}
}

Related

Spring cache for specific values #Cacheable annotation

I want to cache a result of a method only when the attribute of the result contains specific values. For example
Class APIOutput(code: Int, message: String)
sealed class Response<out T : Any> : Serializable {
data class Success<out T : Any>(val data: T) : Response<T>()
data class Error(val errorText: String, val errorCode: Int) : Response<Nothing>()
}
#Cacheable(
key = "api-key",
unless = "do something here"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}
In the above method, I want to cache the response only when Response.Success.data.code == (long list of codes).
Please note, in the previous line data is nothing but APIOutput object. How could I achieve it using unless or any other approach. I was thinking of writing a function that takes a doApicall method result as input and would return true or false and call that method it as unless="call a method". But I'm not sure how to do it. Any help is highly appreciated.
You can specify an expression to be evaluated in unless using SpEL. The returned value is available as result so you can do something like -
#Cacheable(
key = "api-key",
unless = "#result!=null or #result.success.data.code!=200"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}
You can even use Regex in SpEL and can create custom Expression parsers if the existing functionality is not enough for your usecase.
Thanks Yatharth and John! Below is the condition that worked for me. resultcodes in the below expression is a list
#Cacheable(
key = "api-key",
unless = "!(#result instanceof T(com.abc.Response\$Success))
or (#result instanceof T(com.abc.Response\$Success)
and !(T(com.abc.APIStatus).resultCodes.contains(#result.data.code)))"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}

Difference between "def" and "static def" in Gradle

As the title, what is exactly the difference of these two defs in Groovy?
Maybe it's a documentation problem, I can't find anything...
A method declaration without static marks a method as an instance method. Whereas a declaration with static will make this method static - can be called without creating an instance of that class - see https://www.geeksforgeeks.org/static-methods-vs-instance-methods-java/
def in groovy defines a value as duck typed. The capabilities of the value are not determined by its type, they are checked at runtime. The question if you can call a method on that value is answered at runtime - see optional typing.
static def means that the method will return a duck typed value and can be called without having instance of the class.
Example:
Suppose you have these two classes:
class StaticMethodClass {
static def test(def aValue) {
if (aValue) {
return 1
}
return "0"
}
}
class InstanceMethodClass {
def test(def aValue) {
if (aValue) {
return 1
}
return "0"
}
}
You are allowed to call StaticMethodClass.test("1"), but you have to create an instance of InstanceMethodClass before you can call test - like new InstanceMethodClass().test(true).

why can quotes be left out in names of gradle tasks

I don't understand why we don't need to add quotes to the name of gradle task when we declare it
like:
task hello (type : DefaultTask) {
}
I've tried in a groovy project and found that it's illegal, how gradle makes it works.
And I don't understand the expression above neither, why we can add (type : DefaultTask), how can we analyze it with groovy grammar?
As an example in a GroovyConsole runnable form, you can define a bit of code thusly:
// Set the base class for our DSL
#BaseScript(MyDSL)
import groovy.transform.BaseScript
// Something to deal with people
class Person {
String name
Closure method
String toString() { "$name" }
Person(String name, Closure cl) {
this.name = name
this.method = cl
this.method.delegate = this
}
def greet(String greeting) {
println "$greeting $name"
}
}
// and our base DSL class
abstract class MyDSL extends Script {
def methodMissing(String name, args) {
return new Person(name, args[0])
}
def person(Person p) {
p.method(p)
}
}
// Then our actual script
person tim {
greet 'Hello'
}
So when the script at the bottom is executed, it prints Hello tim to stdout
But David's answer is the correct one, this is just for example
See also here in the documentation for Groovy
A Gradle build script is a Groovy DSL application. By careful use of "methodMissing" and "propertyMissing" methods, all magic is possible.
I don't remember the exact mechanism around "task ". I think this was asked in the Gradle forum (probably more than once).
Here is the code that make the magic possible and legal.
// DSL class - Gradle Task
class Task {
def name;
}
// DSL class - Gradle Project
class Project {
List<Task> tasks = [];
def methodMissing(String name, def args) {
if(name == "task"){
Task t = new Task(name:args[0])
tasks << t
}
}
def propertyMissing(String name) {
name
}
}
// gradle build script
def buildScript = {
task myTask
println tasks[0].name
}
buildScript.delegate = new Project()
// calling the script will print out "myTask"
buildScript()

Grails validate fields with default values

We have a class like this in a Grails 2.4.3 application (migrated from 2.3.8):
#Validateable
class Foo {
Integer noDefault;
Integer withDefault = 1;
static constraints = {
noDefault(nullable:false)
withDefault(nullable:false)
}
}
This class is being instantiated in a complex configuration mechanism using a Map like this:
[
noDefault: 0,
withDefault: 2
]
(In fact the Map is part of a huge one, but the class constructor sees this small one.) Formerly the class worked if we omitted the withDefault entry from the config map, using the default value which is not null. In Grails 2.4.3, however, it tells me that this field cannot be null. I can fix it by letting it be null in the constraint, but it lets setting the explicite value null (and overwrite the default value), which causes problem during operation.
Do you know some workaround, which preserves the semantics and correct operation?
Thanx in advance, best regards: Balázs
What you are describing is not consistent with what I would expect and not consistent with the behavior I am seeing. The project at https://github.com/jeffbrown/validatedefaults contains the following code.
At https://github.com/jeffbrown/validatedefaults/blob/master/src/groovy/demo/Foo.groovy
// src/groovy/demo/Foo.groovy
package demo
import grails.validation.Validateable
#Validateable
class Foo {
Integer noDefault;
Integer withDefault = 1;
static constraints = {
noDefault(nullable:false)
withDefault(nullable:false)
}
}
The test at https://github.com/jeffbrown/validatedefaults/blob/master/test/unit/demo/FooSpec.groovy passes:
// test/unit/demo/FooSpec.groovy
package demo
import spock.lang.Specification
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
#TestMixin(GrailsUnitTestMixin)
class FooSpec extends Specification {
void 'test validating default values'() {
given:
def map = [noDefault: 0]
def foo = new Foo(map)
expect:
foo.validate()
}
}
When I run the app I get the same behavior.
// grails-app/conf/BootStrap.groovy
import demo.Foo
class BootStrap {
def init = { servletContext ->
def map = [noDefault: 0]
def foo = new Foo(map)
// this prints true...
println "Foo is valid? : ${foo.validate()}"
}
def destroy = {
}
}
I hope that helps.

How can i place validations for fields in groovy for specific format

I have Domain class and in that for a particular field of type String, it accepts alphanumeric values,i need a validation in the format it should accept only AB12-QW-1 (or) XY-12 values. how can i validate the field.
Please suggest the solution.
Thanks.
Assume your domain class looks like this
class Foo {
String bar
}
If you can define a regex that matches only the legal values, you can apply the constraint using:
class Foo {
String bar
constraints = {
bar(matches:"PUT-YOUR-REGEX-HERE")
}
}
Alternatively, if you can easily list all the legal values, you can use:
class Foo {
String bar
constraints = {
bar(inList:['AB12-QW-1', 'XY-12'])
}
}
If neither of these solutions will work, then you'll likely need to write a custom validator method
You could use a custom validator:
class YourDomain {
String code
static constraints = {
code( validator: {
if( !( it in [ 'AB12-QW-1', 'XY-12' ] ) ) return ['invalid.code']
})
}
}
However, your explanation of what codes are valid is a bit vague, so you probably want something else in place of the in call
[edit]
Assuming your two strings just showed placeholders for letters or numbers, the following regexp validator should work:
constraints = {
code( matches:'[A-Z]{2}[0-9]{2}-[A-Z]{2}-[0-9]|[A-Z]{2}-[0-9]{2}' )
}
And that will return the error yourDomain.code.matches.invalid if it fails

Resources