A Legacy Notes Developer's journey into madness.

Confusing the not very intelligent Expression Language Processor

Devin Olson  November 6 2014 06:14:12 PM
If you write a lot of Java (and I do), you will likely find yourself making use of Method Overloading on a regular basis.  But beware, for with great power comes great responsibility, and part of that responsibility involves writing your code so that the "STUPID #&$%ing EL PROCESSOR doesn't crap out.

Yes, I know I was recently praising the intelligence of the ELP, but the ELP has a few quirks.  One of those quirks is that the ELP doesn't properly COMPLETE it's introspection check.  What I mean here is that when the ELP is checking a Java Object for the existence of a given method, it ONLY checks to see if that method exists.  Once it finds a match for the method, it stops.  It does not check to see if the method is overloaded, nor does it check to see if the method has any arguments.  It just...frakin...stops.  

Now normally this would not be a problem, but if you decide to overload a method this can (and will) jump up and bite you in the ass.  How do I know this?  Indulge me for a bit more and i will explain.  

I have a Java class that I use for configuration management, and a fine class it is. It gets (and if I need it to, sets) configuration information across a multitude of sources.   It has a method called getDescription(), which returns a List of Strings containing description information.  It also has a method called  getDescriptionString(), which as you have probably guessed, returns a String consisting of concatenated description information.  

Behold, actual source code:

public String getDescriptionString() {
  return Core.join(this.getDescription(), " \n");
}


I also have an XPage upon which exists a Repeat Control.   The Repeat Control displays information from multiple Configuration objects, and some of this information includes the description (to keep this code simple, I have removed facets and other non-relevant bits of XML).
I have a Bean object instance called XSPqueueSet, which has a method called getQueueHandles().  This method returns an iterable set, of which each member is an instance of class Queue.  

<xp:repeat
  id="repeat1"
  indexVar="rowIDX"
  var="rowData"
  value="#{XSPqueueSet.queueHandles}">

<xp:text
   escape="true"
   id="description1"
   value="#{rowData.descriptionString}"/>
</xp:repeat>
 

So far everything was working awesome.   Until I realized that some of the descriptions being returned were thousands of characters long.  

This made my nice concise display look crappy.   So I decided to add an additional method getShortDescription() that would cut down on the amount of text being returned. I also realized that I might need to adjust the amount of text being returned in the future, so I overloaded the getDescriptionString() method to accept a maximum length argument as well.  These methods are as follows:

public String getShortDescription() {
  return this.getDescription(80);
}

public String getDescription(final int maxlength) {
  if (maxlength < 1) { return this.getDescriptionString(); }
  if (maxlength < 4) { return "..."; }

  final String string = this.getDescriptionString();
  if (string.length() <= maxlength) { return string; }

  final String left = string.substring(0, maxlength - 3);
  final int idx = left.lastIndexOf(' ');
  final StringBuffer sb = new StringBuffer((idx > 0) ? left.substring(0, idx) : left);
  sb.append("...");

  return sb.toString();
}


I then changed the EL in my markup to reference the new method:


<xp:text
  escape="true"
  id="description1"
  value="#{rowData.shortDescription}"/>


So far this was working out great.  I tested the page, and it was beautiful.  The descriptions in the repeat that were super long before were now all nicely trimmed up and looked great.  I was happy.  

Until I opened another page that was displaying the full description.   Then I was not happy at all.  
Image:Confusing the not very intelligent Expression Language Processor


ARRGGHHH!   We have all had these, but they still frustrate the heck out of me.    
Here is the offending markup:

<xp:label
   id="lblDescription"
   for="description1"
   value="Description" />

<xp:text
   escape="true"
   id="description1"
   value="#{queue.descriptionString}"/>

Now this is code that had NOT CHANGED.  This code was working before I updated the Java object, but was now failing.   It took me a while to figure out, but I finally began to suspect the ELP as the culprit; and it was getting confused about the which of the two versions of the getDescriptionString() method to use.  

I made a quick change to my markup to verify this:

<xp:text
   escape="true"
   id="description1"
   value="#{javascript:queue.getDescriptionString();}"/>

And it worked perfectly -because the ELP was removed from the process.

What does this mean?   Well, the ELP gets confused when your code references an overloaded method, because the ELP is not smart enough to know which particular method signature is being referenced.   If the ELP were to perform a complete introspection, it would be able to determine which method signature to use -based on the parameters (or lack thereof) passed to the method.   But because it just...frakin...stops, it simply cannot work with overloaded methods.

Now, I could have left my quick fix in place and called it done, but there are two reasons that isn't the proper solution,

1) It involves SSJS, which I am trying to avoid
2) More importantly, there could be other EL code in place which references the descriptionString method, and I would have to go hunting all of that down as well.  

No, the better solution here is to change my back-end Java code so that it allows the stupid ELP to work.   Which means replacing the overloaded method name:

public String getShortDescription() {
  return this.getAbridgedDescription(80);
}

public String getAbridgedDescription(final int maxlength) {
  if (maxlength < 1) { return this.getDescriptionString(); }
  if (maxlength < 4) { return "..."; }

  final String string = this.getDescriptionString();
  if (string.length() <= maxlength) { return string; }

  final String left = string.substring(0, maxlength - 3);
  final int idx = left.lastIndexOf(' ');
  final StringBuffer sb = new StringBuffer((idx > 0) ? left.substring(0, idx) : left);
  sb.append("...");
  return sb.toString();
}



This is not a solution I am happy with, but until the ELP is enhanced so it performs a complete introspection check, this will have to do.