This is the second post on my proof-of-concept “Lomboker Eclipse Plugin”

See previous post here


Yesterday, I have shown the first post and the plugin to a fiend of mine, and we had this discussion:

My Friend> is it all Lombok is about?
Me> Well, no. Lombok also has the amazying val and var features, and much more
My Friend> It is not supported in your plugin ?
Me> Well, yes it is true that would be easy to do.
  But sorry about that, I only started yesterday and I spent just 3 hours on it...
  The longer was to write the post on my github.io
My Friend> Oh! OK! I see... that's not so bad. It is even pretty good.

Isn’t it so encouraging to do the second step ?

Day 2 : Transforming val and var for local variable and foreach

On day 2, I worked again to improve this proof-of-concept plugin, and added ~3 more hours of work.

I have implemented the detections of "SomeLongTypeName localVar = new SomeLongTypeName();", and replacements with lombok "var localVar = new SomeLongTypeName();"

Isn't it true we ALL java developper are fed up typing things like ??

Map<String,Map<Integer,List<Boolean>>> map = new HashMap<String,Map<Integer,List<Boolean>>>();
Map<Integer,List<Boolean>> elt1 = map.get("key1");
instead of
val map = new HashMap<String,Map<Integer,List<Boolean>>>();
val elt1 = map.get("key1");

Of course, Java will continue to evolve (slowly as it always did), and there is some hope with this JEP (http://openjdk.java.net/jeps/286) that it will be built-in in the langage soon:

Back to my plugin development, this was pretty easy, but I had to avoid few nasty cases:

  • it is useless to replace already primitive types, and also String type
    int i = 10;
    // should not replace by
    var i = 10; 
  • when using the jdk8 Diamond syntax, I had to preserve the type from the left hand side declaration, to put it to the right hand side!
    List<SomeLongTypeName> ls = new ArrayList<>();
    // => in Lombok:
    var ls = new ArrayList<SomeLongTypeName>();
    // this would be non-sense   
    // var ls = new ArrayList<>();
    
    // this also work..
    Map<String,List<SomeLongTypeName>> map = new HashMap<>();
    // => in Lombok:
    var map = new HashMap<String, List<SomeLongTypeName>>();
    To my opinion, this is a nasty mistake in java8 syntax, it is more logic for typed-inference langage to be like
    List<> ls = new ArrayList<SomeLongTypeName>();
  • final variable must be replaced by "val", whereas modifiable variable must be replaced by "var"
  • for explicitely "final" variable, the keyword modifier "final" must be removed
    final SomeLongTypeName x = new SomeLongTypeName();
    // => in Lombok:
    val x = new SomeLongTypeName();
    // instead of ...   final var x = new SomeLongTypeName(); 
  • I should also detect "effective final" variables (variables that not explicitely marked as final, but bever modified anyway).. I did not implement it yet, so they still are replaced with lombok "var".

Enough speaking ... Demo

Now the settings dialog window has 2 checkboxes:

Click on Preview, to see how local variables are transformed
(notice the different cases using primitive, final declaration, diamond syntax ..)


And also the same with foreach loops

Want to see how code is simple ?

Here is an extract of the transformation of foreach "var" ... It is only ~ 50 lines of code only !
	protected void doRefactorUnit_ValVar(CompilationUnit unit) {
		RequiredLombokImports requiredImports = new RequiredLombokImports();
		ASTVisitor vis = new ASTVisitor() {
			@Override
			public boolean visit(EnhancedForStatement node) {
				SingleVariableDeclaration forVarDecl = node.getParameter();
				boolean needReplaceByVal = needReplaceTypeNameToVar(forVarDecl.getType());
				if (needReplaceByVal) {
					// do replace "for(Type varName : ..) .." => "for(var varName : ..) .."
					AST ast = unit.getAST();
					forVarDecl.setType(ast.newSimpleType(ast.newName(LOMBOK_VAL)));
					requiredImports.setUseVal();
				}				
				return super.visit(node);
			}
		};
		unit.accept(vis);
		requiredImports.addImports(unit);
	}

	private static boolean needReplaceTypeNameToVar(Type varDeclType) {
		String varDeclTypeName = MatchASTUtils.matchSimpleTypeName(varDeclType);
		if (varDeclTypeName != null && (varDeclTypeName.equals(LOMBOK_VAL) || varDeclTypeName.equals(LOMBOK_VAR))) {
			return false; // ok, already a lombok type!
		}
		if (varDeclType.isPrimitiveType() || "String".equals(varDeclTypeName)) {
			// do not replace already primitive type int, double, boolean, and also String...  
			return false;
		}
		return true;
	}
	
	private static class RequiredLombokImports {
		private boolean useLombokVal;
		public void setUseVal() {
			useLombokVal = true;
		}
		public void addImports(CompilationUnit unit) {
			if (useLombokVal) {
				JavaASTUtil.addImport(unit, "lombok.val");
			}
		}
	}

Conclusion

I am ready, and I would be proud to present it at the great Devoxx France 2017 conference.

Maybe somebody will notice it, and this plugin will be improved and added to the excellent Lombok tool suite.

Notice that there was already an official "delomboker" tools, to transform lombok code to plain-old-java code, as required for source-code analyser tools like GWT and Sonar.
Now there would be an official "lomboker" tool.

For the presentation title, I have chosen "Chérie, J'ai Lomboké les Classes" (french translation of "Honey, I Shrunk the Classes"), which is a reference to the film "Honey, I Shrunk the Kids".

Reminder, it is open-source, fork it on GitHub https://github.com/Arnaud-Nauwynck/mytoolbox .. fr.an.eclipse.tools.lombok