JSP vs. Snippetory

I’m pretty aware, JSP is mature enough to get things done. However, working with passive templates I’ve learned, it can be done somewhat easier. At least for developers with not too much experience in using JSP. One of the problems is its complexity. Tons of tags, expression language and, the diversity provided by different tag vendors. All this glued together by a primitive platform with little support for work related to templates. However, it provides the possibility to take a text block and deal with it. But locale handling and output consistency are missing.

If you need precise information things get even more complicated. Is an empty array empty for the EL empty keyword? Takes some time to find it out. And a number of quirks every day. Want to express the size of a collection? You need an additional field some where. And the tool support, considered one of main advantages, is not very fun. At least in while the HTML support is sufficient and java even great, JSP doesn’t really do the job. Opening a field definition accessed via EL? Nope. Opening a tag definition? Nope. Adding library definition when entering name space shortcut? Nope. Re-factoring a piece of code into a JSP tag? Again, not there. OK, I didn’t submit. I did never try to make this better. But the fact is the community does not preserve the power to support JSP adequately. And the complexity of JSP makes those additions more important the less complex technologies. 

But enough complaining. Let’s implement the interface example using JSP. Generally it is common to implement a special view model and fill it in the so-called controller. This in necessary to get the interface a little more explicit, and to decouple execution order and presentation order. And, due to the reduced functionality of EL, the real model is not expressive enough to take over logic responsibilities. In fact this is a price we pay whenever using JSP, though I have to include this in the example. Next question is where to write this logic, i.e. How does the controller look like?

package org.jproggy.examples.inftfgen.jsp;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

/**
 * Servlet Filter implementation class InterfaceGenController
 */
@WebFilter("/intfgen.jsp")
public class InterfaceGenController implements Filter {

    /**
     * Default constructor.
     */
    public InterfaceGenController() {
    }

	public void destroy() {
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		try {
			Class<!--?--> intf = Class.forName(request.getParameter("intf"));
			Set<String> types = new TreeSet<String>();
			InterfaceDefinition interfaceDefinition = new InterfaceDefinition();
			interfaceDefinition.setName(intf.getSimpleName());
			interfaceDefinition.setPackageName(intf.getPackage().getName());
			Collection availablePackages = new HashSet();
			availablePackages.add(intf.getPackage().getName());
			availablePackages.add("java.lang");

			for (Class<?> parent: intf.getInterfaces()) {
				if (!availablePackages.contains(parent.getPackage())) {
					types.add(parent.getName());
				}
				interfaceDefinition.addParent(parent.getSimpleName());
			}

			for (java.lang.reflect.Method refMethod : intf.getDeclaredMethods()) {
				Method method = copyMethod(types, availablePackages, refMethod);
				interfaceDefinition.addMethod(method);
			}

			groupTypes(types, interfaceDefinition);

			// pass the request along the filter chain
			chain.doFilter(request, response);
		} catch (Exception e) {
			throw new IOException(e);
		}

	}

	private void groupTypes(Set types,
			InterfaceDefinition interfaceDefinition) {
		String oldPrefix = "";
		ImportGroup group = null;
		for (String type : types) {
			String prefix = type.split("\\.")[0];
			if (!oldPrefix.equals(prefix)) {
				if (group != null) interfaceDefinition.addImportgroups(group);
				group = new ImportGroup();
			}
			group.addImport(type);
		}
		if (group != null) interfaceDefinition.addImportgroups(group);
	}

	private Method copyMethod(Set types, Collection availablePackages,
			java.lang.reflect.Method refMethod) {
		Method method = new Method();
		method.setName(refMethod.getName());
		Class<!--?--> returnType = refMethod.getReturnType();
		if (!availablePackages.contains(returnType.getPackage())) {
			types.add(returnType.getName());
		}
		method.setName(returnType.getSimpleName());
		for (Class<?> type : refMethod.getParameterTypes()) {
			if (!availablePackages.contains(type.getPackage())) {
				types.add(type.getName());
			}
			method.addParamterType(type.getSimpleName());
		}
		return method;
	}

	public void init(FilterConfig fConfig) throws ServletException {
	}

	public static class InterfaceDefinition {
		String packageName;
		String name;
		List<ImportGroup> importgroups = new ArrayList();
		List<String> parents = new ArrayList<String>();
		List<Method> methods = new ArrayList<Method>();
		public String getPackageName() {
			return packageName;
		}
		public void setPackageName(String packageName) {
			this.packageName = packageName;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public List getImportgroups() {
			return importgroups;
		}
		public void addImportgroups(ImportGroup group) {
			importgroups.add(group);
		}
		public List getParents() {
			return parents;
		}
		public void addParent(String parent) {
			parents.add(parent);
		}
		public List getMethods() {
			return methods;
		}
		public void addMethod(Method method) {
			methods.add(method);
		}
	}
	public static class ImportGroup {
		List<String> imports = new ArrayList<String>();

		public List getImports() {
			return imports;
		}

		public void addImport(String imp) {
			this.imports.add(imp);
		}
	}
	public static class Method {
		String name;
		String returnType;
		List paramterTypes = new ArrayList();
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public String getReturnType() {
			return returnType;
		}
		public void setReturnType(String returnType) {
			this.returnType = returnType;
		}
		public List getParamterTypes() {
			return paramterTypes;
		}
		public void addParamterType(String paramterType) {
			this.paramterTypes.add(paramterType);
		}
	}
}

As I don’t want to advertise for a special framework, and using any of them would be an overkill in such a simple example I decided to use a filter. For simplicities sake I will add the model classes as inner classes to the filter. It’s still quite a bit of code. The model makes half of the lines, but, granted, not that much of the complexity.

After the data is gathered we can start looking at the JSP. This will not really look like a template. And to get the white-spaces controlled I’ll have to write somewhat awkward:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
%>package ${interfaceDefinition.packageName};

<c:forEach var="group" items="${interfaceDefinition.importgroups}"
><c:forEach var="import" items="${group.imports}"
> import ${import};
</c:forEach>
</c:forEach>public interface ${interfaceDefinition.name} extends <c:forEach var="parent"
items="${interfaceDefinition.parents}" varStatus="status"
>${parent}<c:if test="${not status.first}">, </c:if
></c:forEach> {
<c:forEach var="method" items="${interfaceDefinition.methods}"
>  ${method.returnType} ${method.name}( <c:forEach var="parameterType" items="method.parameterTypes"
varStatus="status"><c:if test="${not status.first}"
>, </c:if>${parameterType} arg${status.index}</c:forEach> );
</c:forEach>}

To me it’s kind of hard to figure out this is a template for an interface definition even though I’ve already seen some interface definitions and some templates, too. Let’s look at the Snippetory template again:

package {v:packageName};

<t:imports>
import {v:name};
</t:imports>

public interface {v:name} extends {v:parent delimiter=", "} {
<t:methods>
  {v:return_type} {v:name}(<t:parameters delimiter=", ">{v:type} {v:name}</t:parameters>);
</t:methods>
}

In this case it’s clear on first sight. A template for an interface definition. No doubt about it. While the Java code is, ignoring the model, quite similar in quantity, the difference on template side is drastic. Even though it’s not just the sheer amount code, but there is an elegance that make our work really a bit simpler. A location mark for instance is a real template construct with support for escaping according to the surrounding encoding (like the <c:out/> tag does for XML and only for XML), formatting, and nice little helper like the delimiter attribute, that solves a typical template problem.

Of course, all the brilliant tags provided by all those people will not work on Snippetory. But I’ve never used one of that is not really simple to implement. Most are simply workarounds for JSP problems. But yes, there are some valuable tags, too, that would require a significant effort to re-implement using Snippetory. But the about 350 pages I deal and dealt with do not use them. And they’ve been implemented by quite different people. I skip performance tests here. On one hand I expect JSP to be a little bit faster. But on the other hand the lower implementation effort wights this pretty well. Performance is more interesting to me where the programming model is similar. And of course where Snippetory can win. That’s all for today. Maybe I’ll get back to JSP some day and try another example.

Have fun,

Sir RotN

About these ads