instanceof check in EL expression language
Asked Answered
A

7

39

Is there a way to perform an instanceof check in EL?

E.g.

<h:link rendered="#{model instanceof ClassA}">      
    #{errorMessage1}
</h:link>
<h:link rendered="#{model instanceof ClassB}">      
    #{errorMessage2}
</h:link>
Amarillis answered 25/4, 2012 at 10:55 Comment(0)
A
59

You could compare Class#getName() or, maybe better, Class#getSimpleName() to a String.

<h:link rendered="#{model['class'].simpleName eq 'ClassA'}">      
    #{errorMessage1}
</h:link>
<h:link rendered="#{model['class'].simpleName eq 'ClassB'}">      
    #{errorMessage2}
</h:link>

Note the importance of specifying Object#getClass() with brace notation ['class'] because class is a reserved Java literal which would otherwise throw an EL exception in EL 2.2+.

The type safe alternative is to add some public enum Type { A, B } along with public abstract Type getType() to the common base class of the model.

<h:link rendered="#{model.type eq 'A'}">      
    #{errorMessage1}
</h:link>
<h:link rendered="#{model.type eq 'B'}">      
    #{errorMessage2}
</h:link>

Any invalid values would here throw an EL exception during runtime in EL 2.2+.

In case you're using OmniFaces, since version 3.0 you could use #{of:isInstance()}.

<h:link rendered="#{of:isInstance('com.example.ClassA', model)}">      
    #{errorMessage1}
</h:link>
<h:link rendered="#{of:isInstance('com.example.ClassB', model)}">      
    #{errorMessage2}
</h:link>
Ayacucho answered 25/4, 2012 at 13:47 Comment(6)
@Lucas: it's impossible if the implementation is an interface :) If you've a problem with it, it's caused by something else.Ayacucho
The Class string test suggestion is useful in some situations, but it does not work for interfaces or for subclasses. There is a real and urgent need for instanceof. Please vote here: java.net/jira/browse/JSP_SPEC_PUBLIC-113. See also related problem, how call a composite component bound to a type only for a matching type using some kind of test other than 'rendered', which does not seem to work during build phase (remarks at bottom of stackoverflow.com/questions/16665705/…)Vahe
What about hierarchical inheritance. I have an Item class which has two sub classes, namely Product and Service. In turn Product class has two subclasses named Medicine and Consumable. So I want to check weather a medicine entity is a Product ? It will return true only if I check it to be a Medicine, but not when checked as a Product.Christhood
@jonnieZG: Nope, use the type safe alternative as shown in answer's second part (in other words, works for all types/subclasses).Ayacucho
@Ayacucho I don't think that your second answer is "type safe". To take the example from the comment above - your proposal would return "Medicine", even if I sometimes need to check for "Product". Despite the fact that I'm not always able to modify a class. And model['class'].simpleName can lead to spurious errors in environments that use proxies (like JPA). Thus I came up with my own solution. I wonder why OmniFaces doesn't include something similar, for example a custom EL function.Pedicular
@Tobias: OmniFaces has an of:isInstance().Ayacucho
R
6

That doesn't work in EL. Use the backing bean for this:

public class MyBean {

    public boolean getIsClassA() {
        if(model instanceof ClassA) {
            return true;
        }
        return false;
    }


}

And then do the check by calling the backing bean:

<h:link outcome="#{PageNameA}?faces-redirect=true&amp;" rendered="#{myBean.isClassA}">      
    #{errorMessage}
</h:link>

Rolph answered 25/4, 2012 at 11:1 Comment(3)
This is indeed a good answer. I howerver adopted another solution: rendered="#{listRow.rowData.getClass().getName() == 'ClassB'}" (ClassB must be fully quelified). Is there something strong against this "not-so-elegant" solution?Amarillis
I think this solution is better because it's easier to understand, but that depends somewhat on the intent. If your intent is to display only one link or the other, then put just one link in the view and use a backing bean for the business logic that decides what the outcome should be. This is also more testable. If you want to possibly display both links, then the other solution works OK, but I would still prefer the backing bean for testability and clarity. You have a perfectly good language (java) so there's no need to do crazy things and hard-code class names in EL.Pyrogenous
This is a more general solution than the (sometimes still useful) EL-based Class String test suggested by BalusC, since can work for interfaces and subclasses, but it is pain to have to do it all the time, and it often pollutes both the backing beans and/or entities (or wherever one places the test). We need instanceof in JSF2.x a.s.a.p !Vahe
S
4

it works:

rendered="#{node.getClass().getSimpleName() == 'Logt_anno'}"
Silverware answered 18/10, 2013 at 16:34 Comment(0)
D
3

Define a static function like:

public boolean isInstanceOf( Object obj, Class targetClass) {
        return targetClass.isInstance(obj);
    }

Define a custom EL function for it, and use that. We could also pass a string name and do a forName() inside the method.

Duvalier answered 18/11, 2015 at 13:48 Comment(0)
H
2

There is a way, see

JSF EL: instanceof reserved but not yet implemented?

However, the instanceof operator is still not implemented, at least in Mojarra 2.1. Please vote for the bug here:

http://java.net/jira/browse/JSP_SPEC_PUBLIC-113

The best workaround currently is probably to store the class name in a backing bean getter instead of creating a boolean test method for each class:

public String getSelectedNodeClassName()
{
    return selectedNode.getClass().getSimpleName();
}

So it would be a mix of BalusC's and flash's solutions. It would however be much more readable in JSF than BalusC's plus it pretty much resembles the future instanceof operator use:

rendered="#{nodeManager.selectedNodeClassName eq 'ChapterNode'}"

This will not produce one method per class test on the backing bean as flash suggested. This could be slower than flash's though.

Haggar answered 3/5, 2012 at 5:36 Comment(0)
R
1

Not very elegant as it mixes JSP EL and the earlier expression syntax, but doesn't require any extra Java code:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<c:set var="interfaceClass" value="<%=com.example.ClassA.class%>"/>
<c:set var="implementationClass" value="${model['class']}"/>
<c:if test="${interfaceClass.isAssignableFrom(implementationClass)}">
    <%-- Your logic here. --%>
</c:if>
Ramsdell answered 31/5, 2017 at 14:31 Comment(0)
P
0

You could use a helper bean for that:

@ManagedBean
public class Helper {
  public boolean isInstance(Object bean, String fullyQualifiedClassName) {
    return Class.forName(fullyQualifiedClassName).isInstance(bean);
  }
}

Usage:

<h:link rendered="#{helper.isInstance(model, 'package.ClassA')}">
  #{errorMessage1}
</h:link>

This has the advantage that inheritance is taken into account and you can test for classes which you can't modify (both disadvantages of the solutions of BalusC).

If you like to use the simple class name (and don't fear name clashes), you could use a lookup map which you fill by hand or with a class path scanner like org.reflections:

@ManagedBean
@ApplicationScoped
public class Helper {
  private Map<String, Class<? extends MyBaseClass>> classes = 
      new Reflections("myrootpackage").getSubTypesOf(MyBaseClass.class).stream()
      .collect(Collectors.toMap(Class::getSimpleName, Function.identity()));

  public boolean isInstance(Object bean, String simpleClassName) {
    final Class<? extends MyBaseClass> c = this.classes.get(simpleClassName);
    return c != null && c.isInstance(bean);
  }
}

You could even move the helper function to an ELResolver:

public class InstanceOfELResolver extends ELResolver {

  public Object invoke(final ELContext context, final Object base, 
      final Object method, final Class<?>[] paramTypes, final Object[] params) {
    if ("isInstanceOf".equals(method) && params.length == 1) {
      context.setPropertyResolved(true);
      try {
        return params[0] != null && Class.forName(params[0].toString()).isInstance(base);
      } catch (final ClassNotFoundException e) {
        return false;
      }
    }
    return null;
  }

  // ... All other methods with default implementation ...
}

Usage:

<h:link rendered="#{model.isInstanceOf('package.ClassA')}">
  #{errorMessage1}
</h:link>
Pedicular answered 1/6, 2018 at 9:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.