What is "String toString() { .. }"? - groovy

So can you please help me to understand what String toString() { "$email" } will do in the following program???
class User
{
Long id
Long version
String email
String password
String toString()
{ "$email" }
def constraints =
{
email(email:true)
password(blank:false, password:true)
}
}

It means that whatever is in the email variable will be returned when toString() is called.
It could also be written as:
#Override
String toString() {
email
}
but the writer decided to be "smart" (yes, I'm being sarcastic!) and use the $ notation of embedding a variable into a string.
Remark:
In groovy you don't have to use return - the default behavior is that the last statement inside a method will be returned.

Related

Groovy list with variable name containing a dot (.) gets converted to string

I have a list in groovy, defined as
env.list = ["abc","def"]
If I try using this in a for loop
for (letters in env.list) {
print("Letter is $letters")
}
It will iterate over each letter and print the following -
Letter is [
Letter is "
Letter is a
.....
If I define the list as follows -
list = ["abc","def"]
It will treat this as a list. The for loop will print the following.
Letter is abc
Letter is def
Was using groovy to run my Jenkins pipeline.
Why is there a difference based on the name?
How can we define a list using a variable name with a dot (.)?
in jenkins pipeline the env - is a variable that holds list of environment variables:
https://jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
and environment variable could hold only a string
so when you try to assign to environment variable a list - it automatically converted to string
env.list = ["abc","def"]
equivalent to
env.list = ["abc","def"].toString()
and then you are iterating string by char...
To explain why this behavior happens, it is best to turn to the Jenkins source code.
The global variable name is env which takes us to org.jenkinsci.plugins.workflow.cps.EnvActionImpl.Binder. This binds the value to the script, which in this case is the pipeline.
Source code:
#Override public EnvActionImpl getValue(CpsScript script) throws Exception {
Run<?,?> run = script.$build();
if (run != null) {
return EnvActionImpl.forRun(run);
} else {
throw new IllegalStateException("no associated build");
}
}
EnvActionImpl extends the Groovy type GroovyObjectSupport (source code). GroovyObjectSupport has this is in its documentation:
A useful base class for Java objects wishing to be Groovy objects
So, this is Jenkins allowing a Java implementation to allow it to set it's behavior for Groovy. The methods you are using are public java.lang.Object getProperty(java.lang.String property) and public void setProperty(java.lang.String property, java.lang.Object newValue), so we will look closer at EnvActionImpl's implementation of them.
For setProperty, the implementation is here:
#Override public void setProperty(String propertyName, Object newValue) {
env.put(propertyName, String.valueOf(newValue));
try {
owner.save();
} catch (IOException x) {
throw new RuntimeException(x);
}
}
Looking higher up in the class we see the declaration of env is private final Map<String,String> env;. The propery name is used as the key (list in your example) and the value is the String.valueOf return of newValue, which in your case is the stringified ["abc","def"].
Looking at setProperty:
#Override public String getProperty(String propertyName) {
try {
CpsThread t = CpsThread.current();
return EnvironmentExpander.getEffectiveEnvironment(getEnvironment(), t.getContextVariable(EnvVars.class), t.getContextVariable(EnvironmentExpander.class)).get(propertyName);
} catch (Exception x) {
LOGGER.log(Level.WARNING, null, x);
return null;
}
}
That can be dove into more to understand the EnvironmentExpander and CpsThread mechanics, but the quickest way is to just check the signature - public String.
This explains what Jenkins is doing under the hood with the env variable in pipeline scripts, and why your iteration is happening over a String's characters, and not the list like you might expect. If you created your own variable and tried it yourself, you would see the difference in behavior between, for example Map and the EnvActionImpl type.
final myVar = [:]
myVar.list = ["abc","def"]
env.list = ["abc","def"]
echo "${myVar.list.getClass()}"
echo "${env.list.getClass()}"

Pass method as parameter Groovy

I am creating a Groovy project and I would like to pass a method as a parameter is this possible?
I have 2 methods, which basically do the same thing but have a few small differences, each have different properties.
I would like to send through whether to use the 'buildPayLoad' function or
'buildSecondPayLoad' into my sendEmail function.
private Operation sendEmail(Order order) {
def payload = buildPayload(order, templateId)
}
private firstEmailPayLoad buildPayload(Order order, String templateId) {
new firstEmailPayLoad(
templateId,
config.fromAddress,
order.firstPayLoadProperty,
buildEmailDataFirstPayLoad(order)
).validate()
}
private secondEmailPayLoad buildSecondPayLoad(Order order, String templateId) {
new secondEmailPayLoad(
templateId,
config.fromAddress,
config.bccAddress,
config.otherProperty,
order.secondPayLoadProperty
buildEmailData(order)
).validate()
}
You can just do:
private Operation sendEmail(Order order, Closure builder) {
def payload = builder(order, templateId)
}
Then call it with:
sendEmail(order, this.&buildPayload)
Or
sendEmail(order, this.&buildSecondPayLoad)
BTW: You really should start class names with a capital letter, so instead of secondEmailPayLoad, you should call it SecondEmailPayLoad (and the same with firstEmailPayLoad)

Groovy Mixin use method of mixed class

Consider a mixin class
class StringPlusMixin {
String plus(String other) {
return toString() + other
}
}
And his use case
#Mixin(StringPlusMixin)
class POGO {
String descr
String toString(){
return descr
}
}
Is there some way to make SringPlusMixin to use POGO#toString() instead of SringPlusMixin#toString() ?
The actual output is:
POGO pogo = new POGO(descr: "POGO description");
System.out.println(pogo + "Some message."); //StringPlusMixin#f410f8 Some message.
I'm considering to use this mixin since the Groovy default of instance + String is to try to call a plus() method. I'm using my POGO in several Java classes and trying to not need to change all messages to use toString().
Use #Delegate transformation in POGO.
#Mixin(StringPlusMixin)
class POGO {
#Delegate String descr
String toString(){
return descr
}
}
Since descr is owned by POGO, in runtime use of descr is delgated to the owner which is POGO.
Reflection? Also, i turned your class into a Category, by making the method static and passing the child object as first parameter.
Update: as per comments, removed the type from the mixin method
import org.codehaus.groovy.runtime.InvokerHelper as Invoker
class StringPlusMixin {
static String plus(pogo, String other) {
def toString = pogo.class.declaredMethods.find { it.name == "toString" }
return toString.invoke(pogo) + other
}
}
#Mixin(StringPlusMixin)
class POGO {
String descr
String toString(){
return descr
}
}
pogo = new POGO(descr: "POGO description")
assert pogo + " Some message." == "POGO description Some message."

Overloading a method which accepts `object` as default parameter type

I need to be able to call a method and pass in an object of an unknown type
but then have the correct overload called. I also need a default implementation that accepts
object as its parameter type. What I'm seeing is that the default overload is the only one that ever gets used.
Here's the gist of what I'm trying to do:
class Formatter
{
private object Value;
public Formatter(object val){
Value = val;
}
public override string ToString()
{
return Format(Value);
}
private string Format(object value)
{
return value.ToString();
}
private string Format(DateTime value)
{
return value.ToString("yyyyMMdd");
}
}
Ok, so far so good. Now I want to be able to do this:
public static class FancyStringBuilder()
{
public static string BuildTheString()
{
var stringFormatter = new Formatter("hello world");
var dateFormatter = new Formatter(DateTime.Now);
return String.Format("{0} {1}", stringFormatter, dateFormatter);
}
}
The result of FancyStringBuilder.BuildTheString() is "hello world 2012-12-21 00:00:00.000", when I expected "hello world 20121221"
The problem is that the overload that accepts a DateTime is not being called, instead defaulting to the overload which accepts an object. How can I call the proper method without resorting to a messy switch statement?
In Formatter.ToString(), the override Formatter.Format(object) is always called. This is because the overload resolution happens at compile-time, not run-time. At compile-time, the only thing known about Value is that it's an object.
If you really want to distinguish incoming types, you'll need to do so in Formatter's constructor. In this case, rather than hanging on to the object, you could just call ToString() immediately and only store the formatted result:
class Formatter
{
string formattedValue;
public Formatter(object value)
{
formattedValue = value.ToString();
}
public Formatter(DateTime value)
{
formattedValue = value.ToString("yyyyMMdd");
}
public string ToString()
{
return formattedValue;
}
}
Note that this does assume that your object isn't changing between the time you create the Formatter object and the time Formatter.ToString() is called, or at the very least that it's okay to take a snapshot of the string representation at the time the Formatter is created.
This also assumes that you know the incoming types at compile-time. If you want a truly run-time-only solution, you'll have to use the "is" operator or a typeof() comparison.
If your goal is just to provide custom ToString() formatting based on the incoming type, I'd probably do it using a list that maps from types to format strings:
static class Formatter
{
private static List<Tuple<Type, string>> Formats;
static Formatter()
{
Formats = new List<Tuple<Type, string>>();
// Add formats from most-specific to least-specific type.
// The format string from the first type found that matches
// the incoming object (see Format()) will be used.
AddMapping(typeof(DateTime), "yyyyMMdd");
// AddMapping(typeof(...), "...");
}
private static void AddMapping(Type type, string format)
{
Formats.Add(new Tuple<Type, string>(type, format));
}
public static string Format(object value)
{
foreach (var t in Formats)
{
// If we find a type that 'value' can be assigned to
// (either the same type, a base type, or an interface),
// consider it a match, and use the format string.
if (t.Item1.IsAssignableFrom(value.GetType()))
{
return string.Format(t.Item2, value);
}
}
// If we didn't find anything, use the default ToString()...
return value.ToString();
}
}
With that, calling code then looks like:
Console.WriteLine(
"{0} {1}",
Formatter.Format(DateTime.Now),
Formatter.Format("banana"));
I think this is because the class constructor takes an object as parameter, and then assign that object to variable Value which is also an object. There for calling Format(object) since Value is of type object
Try this
public override string ToString()
{
if(Value is DateTime)
return Format(Convert.ToDateTime(Value)); //this should call the right method
return Format(Value); //works for other non-custom-format types e.g. String
}

Joining multiple strings in Grails

I am new to Grails and beyond a little bit of PHP have little development experience. I'm looking for the best way to combine multiple strings I've been successful using the + operator as follows but am not sure if this is the most effective way. I tried sorting it out using StringBuilder but could not get it to work. Any examples showing that would also be appreciated.
class Person {
String firstName
String middleName
String lastName
static constraints = {
some constriants...
}
String toString() { return lastName + ',' + lastName + ' ' + middlename }
}
Try
return "$lastName,$lastName $middleName"
The Groovy language is very flexible for this improvements, I explain this:
In Java you have:
private String field;
public String toString(){
return "new String" + field;
}
In Groovy as you know the 'public' word is optional but also the keyword 'return' is optional too and we can use a GString implementing the $ operand
In Groovy you have:
String toString(){
"new String $field" // cool, uh
}
Is a lil' of syntactic sugar...
Regards

Resources