Velocity
  1. Velocity
  2. VELOCITY-102

Add Support for Static Utility Classes

    Details

    • Type: Improvement Improvement
    • Status: Resolved
    • Priority: Minor Minor
    • Resolution: Fixed
    • Affects Version/s: 1.3-rc1
    • Fix Version/s: 1.6
    • Component/s: Engine
    • Labels:
      None
    • Environment:
      Operating System: All
      Platform: All

      Description

      Support for Static Utility Classes is:

      1) define namespaces

      context.defineNamespace("Math").add( java.lang.Math.class ) ;
      used as $Math.sin(0)

      2) Add / Replace / remove methods in namespace :

      context.getNamespace("Math").add(MyRandom.class.getMethod("randomString",new
      Class[]

      {int.class}

      ))
      used as $Math.randomString(12)

      3) "union" on namespaces

      context.defineNamespace("Utils")
      .add(context.getNamespace("Math"))
      .add(context.defineNamespace("Collections",Collections.class ) );

      used as:
      $Utils.sin(0)
      $Utils.sort($list)

      4) Global namespace

      context.getGlobalNamespace().add( Math.class );
      used as $sin(0)

      5) inline namespaces:

      #use java.lang.Math as Math
      $Math.sin(0)
      #end

      #with Math
      $sin(0)
      #end

        Activity

        Mark Thomas made changes -
        Workflow Default workflow, editable Closed status [ 12551819 ] jira [ 12552297 ]
        Mark Thomas made changes -
        Workflow jira [ 12324977 ] Default workflow, editable Closed status [ 12551819 ]
        Hide
        Adrian Tarau added a comment -

        Looks ok to me. Thanks Nathan.

        Show
        Adrian Tarau added a comment - Looks ok to me. Thanks Nathan.
        Nathan Bubna made changes -
        Status Open [ 1 ] Resolved [ 5 ]
        Resolution Fixed [ 1 ]
        Hide
        Adrian Tarau added a comment -

        No "automatic names" and nothing changed in Context

        I think I'll change some things in uberspector, nothing more(at least based on what I remember about this functionality).

        I've always wanted something like this, so if there are not objections I will give it a try.

        Show
        Adrian Tarau added a comment - No "automatic names" and nothing changed in Context I think I'll change some things in uberspector, nothing more(at least based on what I remember about this functionality). I've always wanted something like this, so if there are not objections I will give it a try.
        Hide
        Nathan Bubna added a comment -

        java.lang.Math seems like a good enough general example. At least, everyone knows it and its static nature, so the problem is apparent. Though i'm not entirely sure how Adrian intends to do this, I don't think this is likely to take more code than something along the lines of Jim Seach's tweak to UberspectImpl.getMethod above (just fixed to maintain backwards compatibility). Seems like a pretty small code effort (new docs and tests aside) for a feature that appears sufficiently desired by people. As long as someone who wants it does the work, i have no problem with the basic idea. I only object to the "automatic names" idea, as that does open up new complications and implies a change to Context (a very public interface).

        Show
        Nathan Bubna added a comment - java.lang.Math seems like a good enough general example. At least, everyone knows it and its static nature, so the problem is apparent. Though i'm not entirely sure how Adrian intends to do this, I don't think this is likely to take more code than something along the lines of Jim Seach's tweak to UberspectImpl.getMethod above (just fixed to maintain backwards compatibility). Seems like a pretty small code effort (new docs and tests aside) for a feature that appears sufficiently desired by people. As long as someone who wants it does the work, i have no problem with the basic idea. I only object to the "automatic names" idea, as that does open up new complications and implies a change to Context (a very public interface).
        Hide
        Adrian Tarau added a comment -

        Christopher,

        I agree java.lang.Math was not a good example for what I was trying to explain, anyway on second thought letting the user to choose the name is more appropriate. Sometime it is good to enforce users to follow a specific path, but this one is not the right case.

        So I will go with context.put(String, Class) and we will so how it goes after first implementation.

        Show
        Adrian Tarau added a comment - Christopher, I agree java.lang.Math was not a good example for what I was trying to explain, anyway on second thought letting the user to choose the name is more appropriate. Sometime it is good to enforce users to follow a specific path, but this one is not the right case. So I will go with context.put(String, Class) and we will so how it goes after first implementation.
        Hide
        Christopher Schultz added a comment -

        Adrian,

        If you are willing to subclass a certain class to change its "automatic" name, then why not also subclass said class to provide methods to access the static members and methods from a concrete instance of that class?

        I tend to agree with Nathan that custom tools are a good way to go, here. It's unfortunate that you can't create an instance of java.lang.Math (nor can you extend it), but this class was simply not designed to be used in any way other than statically.

        Are we picking java.lang.Math as a truly representative class of useful items in the Java universe? I feel like that example is a bit contrived, and leads us down a path requiring lots of code and special cases to handle a situation that has minimal applicability.

        Show
        Christopher Schultz added a comment - Adrian, If you are willing to subclass a certain class to change its "automatic" name, then why not also subclass said class to provide methods to access the static members and methods from a concrete instance of that class? I tend to agree with Nathan that custom tools are a good way to go, here. It's unfortunate that you can't create an instance of java.lang.Math (nor can you extend it), but this class was simply not designed to be used in any way other than statically. Are we picking java.lang.Math as a truly representative class of useful items in the Java universe? I feel like that example is a bit contrived, and leads us down a path requiring lots of code and special cases to handle a situation that has minimal applicability.
        Hide
        Adrian Tarau added a comment -

        It's fine with me, anyway the users can java-isms their templates if they want at least I think I will do it
        For me, having a java look-a-like version of accessing static members is preferable, but it is more like a matter of taste.

        Show
        Adrian Tarau added a comment - It's fine with me, anyway the users can java-isms their templates if they want at least I think I will do it For me, having a java look-a-like version of accessing static members is preferable, but it is more like a matter of taste.
        Hide
        Nathan Bubna added a comment -

        I can't say i'm fond of the idea for a few reasons. First, the very reasons that i like context.put("math", Math.class) are that it keeps control of the name fully in the hands of the context creator, and that when working on the template, you don't really need to know that $math is anything special. I've long been an advocate of hiding java-isms (like the difference between arrays and lists) from templates. Despite the fact that it is java-powered and allows java methods to be called, VTL is not java. When we do things that make it more like java, then it complicates it and makes it more difficult to swap things around on the java side. So, i prefer the ignorance that comes with something like $view.CONTEXT, as it allows $view to be almost anything and CONTEXT to refer to getCONTEXT() or get("CONTEXT"). This loose binding provides a lot of implementation flexibility that a tighter binding would not. The class can evolve a lot without ever needing to update the template.

        So, i actually think keeping things to $view.CONTEXT simplifies things. No worries about classes with same name and different package, no need to consider if something is accessed through a class or an instance, as there is no actual difference between the way the two behave.

        Show
        Nathan Bubna added a comment - I can't say i'm fond of the idea for a few reasons. First, the very reasons that i like context.put("math", Math.class) are that it keeps control of the name fully in the hands of the context creator, and that when working on the template, you don't really need to know that $math is anything special. I've long been an advocate of hiding java-isms (like the difference between arrays and lists) from templates. Despite the fact that it is java-powered and allows java methods to be called, VTL is not java. When we do things that make it more like java, then it complicates it and makes it more difficult to swap things around on the java side. So, i prefer the ignorance that comes with something like $view.CONTEXT, as it allows $view to be almost anything and CONTEXT to refer to getCONTEXT() or get("CONTEXT"). This loose binding provides a lot of implementation flexibility that a tighter binding would not. The class can evolve a lot without ever needing to update the template. So, i actually think keeping things to $view.CONTEXT simplifies things. No worries about classes with same name and different package, no need to consider if something is accessed through a class or an instance, as there is no actual difference between the way the two behave.
        Hide
        Adrian Tarau added a comment -

        Sorry, I meant to say "scan for static methods/fields of any object present in context and not only for a Class object".

        For example I have a tool ViewTool and I'm using this tool as : context.put("view", new ViewTool()).

        I also have some public static fields and methods : ViewTool.CONTEXT or ViewTool.getPath() and I would like to have those accessible in my templates.

        Maybe even to introduce in Context something like void put(Class staticTool) to allow automatic name creation. In my opinion it would be better to have in templates ViewTool.CONTEXT instead of view.CONTEXT cause you will "know" it is a static member access.Allowing the users to name the tool in the context could lead to some confusion when reading a template. Right now you know that every access to a member is done against an instance but if we introduce access to static members and static access will look like instance access could be confusing.

        Of course it can creates conflicts, two classes with the same name but in different package cannot be used with this automatic naming but on the other hand not every class in your project actually goes in the context.
        As a workaround you can extend the class with static members and provide a different name to be used in the context.Also an exception could be threw in case of conflicts like this one.

        Any thoughts?

        Show
        Adrian Tarau added a comment - Sorry, I meant to say "scan for static methods/fields of any object present in context and not only for a Class object". For example I have a tool ViewTool and I'm using this tool as : context.put("view", new ViewTool()). I also have some public static fields and methods : ViewTool.CONTEXT or ViewTool.getPath() and I would like to have those accessible in my templates. Maybe even to introduce in Context something like void put(Class staticTool) to allow automatic name creation. In my opinion it would be better to have in templates ViewTool.CONTEXT instead of view.CONTEXT cause you will "know" it is a static member access.Allowing the users to name the tool in the context could lead to some confusion when reading a template. Right now you know that every access to a member is done against an instance but if we introduce access to static members and static access will look like instance access could be confusing. Of course it can creates conflicts, two classes with the same name but in different package cannot be used with this automatic naming but on the other hand not every class in your project actually goes in the context. As a workaround you can extend the class with static members and provide a different name to be used in the context.Also an exception could be threw in case of conflicts like this one. Any thoughts?
        Hide
        Nathan Bubna added a comment -

        I also liked context.put("math", Math.class). I'm not sure what you mean by scanning instances for static classes, but public static field support is another conversation and an old one at that. I'm open to it, but let's save it for another bug.

        Anyway, i think if you are going to implement this, then you should go on whichever path you prefer. "Them that do the work make the decisions", as they say. Do what seems best to you.

        Show
        Nathan Bubna added a comment - I also liked context.put("math", Math.class). I'm not sure what you mean by scanning instances for static classes, but public static field support is another conversation and an old one at that. I'm open to it, but let's save it for another bug. Anyway, i think if you are going to implement this, then you should go on whichever path you prefer. "Them that do the work make the decisions", as they say. Do what seems best to you.
        Hide
        Adrian Tarau added a comment -

        I think Geir proposal (context.put("math", Math.class) it's the cleanest possible solution for the end user. I will go even further and even for instances we could scan for static classes/fields(just once per class). The solution should definitely go in the current uberspect implementation.

        I could take a look, but I need to know which path to go...

        Show
        Adrian Tarau added a comment - I think Geir proposal (context.put("math", Math.class) it's the cleanest possible solution for the end user. I will go even further and even for instances we could scan for static classes/fields(just once per class). The solution should definitely go in the current uberspect implementation. I could take a look, but I need to know which path to go...
        Hide
        Nathan Bubna added a comment -

        Is anyone still interested in following through on this? Seems like a good fit for a custom uberspect, especially if chaining support gets finished (see VELOCITY-588). Of course, i'm perfectly happy just using VelocityTools.

        Show
        Nathan Bubna added a comment - Is anyone still interested in following through on this? Seems like a good fit for a custom uberspect, especially if chaining support gets finished (see VELOCITY-588 ). Of course, i'm perfectly happy just using VelocityTools.
        Henning Schmiedehausen made changes -
        Component/s Source [ 12310214 ]
        Bugzilla Id 11924
        Fix Version/s 1.6 [ 12310290 ]
        Component/s Engine [ 12311337 ]
        Assignee Velocity-Dev List [ velocity-dev@jakarta.apache.org ]
        Hide
        Henning Schmiedehausen added a comment -

        Shouldn't be too hard to implement. I like the StaticWrapper idea. Schedule it for 1.6

        Show
        Henning Schmiedehausen added a comment - Shouldn't be too hard to implement. I like the StaticWrapper idea. Schedule it for 1.6
        Jeff Turner made changes -
        Field Original Value New Value
        issue.field.bugzillaimportkey 11924 12314972
        Hide
        Juozas Baliuka added a comment -

        "#with" macro ( idea from Pascal )
        This is not static method specific and can be used
        for bean properties too :

        #with math, formatUtils, bean

        $format( $sin($x) )
        $format( $cos($x/2) )

        #end

        becomes this :

        $formatUtils.format( $math.sin( $bean.x ) )
        $formatUtils.format( $math.cos( $bean.x/2 ) )

        "#with .. as" form:

        #with math, formatUtils, bean as t

        $t.format( $t.sin($t.x) )
        $t.format( $t.cos($t.x/2) )

        #end

        I am not sure about "use/uses" macro it loads class from template and it is not
        very secure and can be problems with ClassLoaders:

        #use java.lang.Math as math

        Some code generation tools provide Velocity templates and it is possible to
        replace them with custom templates, but API working with context is private.
        I know "properties" can help to add tools, but I like this way too.

        Show
        Juozas Baliuka added a comment - "#with" macro ( idea from Pascal ) This is not static method specific and can be used for bean properties too : #with math, formatUtils, bean $format( $sin($x) ) $format( $cos($x/2) ) #end becomes this : $formatUtils.format( $math.sin( $bean.x ) ) $formatUtils.format( $math.cos( $bean.x/2 ) ) "#with .. as" form: #with math, formatUtils, bean as t $t.format( $t.sin($t.x) ) $t.format( $t.cos($t.x/2) ) #end I am not sure about "use/uses" macro it loads class from template and it is not very secure and can be problems with ClassLoaders: #use java.lang.Math as math Some code generation tools provide Velocity templates and it is possible to replace them with custom templates, but API working with context is private. I know "properties" can help to add tools, but I like this way too.
        Hide
        Juozas Baliuka added a comment -

        I like "StaticClassWrapper" idea, namespace stuff can be implemented this
        way too, StaticClassWrapper can be container for static methods :

        public StaticClassWrapper defineNamespace( String name )

        { StaticClassWrapper namespace = new StaticClassWrapper(); put( name, namespace ); return namespace; }

        public class StaticClassWrapper{

        List methods = new Vector();

        public StaticClassWrapper add( Class c )

        { copyStaticMethods( methods, c.getMethods() ); return this; }

        ..........................

        }

        methods can be added/removed this way and used like :

        $math.sin(2)
        $math.median($var)

        some reserved name can be used for "global" methods.

        Show
        Juozas Baliuka added a comment - I like "StaticClassWrapper" idea, namespace stuff can be implemented this way too, StaticClassWrapper can be container for static methods : public StaticClassWrapper defineNamespace( String name ) { StaticClassWrapper namespace = new StaticClassWrapper(); put( name, namespace ); return namespace; } public class StaticClassWrapper{ List methods = new Vector(); public StaticClassWrapper add( Class c ) { copyStaticMethods( methods, c.getMethods() ); return this; } .......................... } methods can be added/removed this way and used like : $math.sin(2) $math.median($var) some reserved name can be used for "global" methods.
        Hide
        Daniel Rall added a comment -

        Jim, that's exactly what I was suggesting. I originally brought it up on the
        commons-dev list as what you'd do to an extension Uberspector which you could
        plug into Velocity. However, it might be reasonable to include this sort of
        functionality in the core. Geir, do you have any thoughts on this?

        Show
        Daniel Rall added a comment - Jim, that's exactly what I was suggesting. I originally brought it up on the commons-dev list as what you'd do to an extension Uberspector which you could plug into Velocity. However, it might be reasonable to include this sort of functionality in the core. Geir, do you have any thoughts on this?
        Hide
        Jim Seach added a comment -

        Daniel, when I first read your email about the wrapper, I didn't understand
        what you were getting at, but I think I do now. In other words, create a
        wrapper class something like:

        public class StaticClassWrapper {
        private Class userClass;

        public StaticClassWrapper(Class userClass)

        { this.userClass = userClass; }

        public StaticClassWrapper(String className) throws ClassNotFoundException

        { this(Class.forName(className)); }

        public Class getUserClass()

        { return userClass; }

        }

        check for that class in UberspectImpl.getMethod(), and pass its wrapped class
        to the introspecter, rather than checking for java.lang.Class and using that
        object as I had suggested earlier.

        Taking it one step further, if we anticipate that others may have a need to
        extend this class, we may want to define an interface, a default wrapper class
        implementing that interface, and a way to override which wrapper class to look
        for with a configuration option, as you suggested. (Of course, we could always
        just make the class final and privatize all the methods in order to avoid the
        maintenance headaches

        If one of the committers thinks this is a reasonable approach, I can try to
        implement it and post a patch this weekend. Let me know.

        Show
        Jim Seach added a comment - Daniel, when I first read your email about the wrapper, I didn't understand what you were getting at, but I think I do now. In other words, create a wrapper class something like: public class StaticClassWrapper { private Class userClass; public StaticClassWrapper(Class userClass) { this.userClass = userClass; } public StaticClassWrapper(String className) throws ClassNotFoundException { this(Class.forName(className)); } public Class getUserClass() { return userClass; } } check for that class in UberspectImpl.getMethod(), and pass its wrapped class to the introspecter, rather than checking for java.lang.Class and using that object as I had suggested earlier. Taking it one step further, if we anticipate that others may have a need to extend this class, we may want to define an interface, a default wrapper class implementing that interface, and a way to override which wrapper class to look for with a configuration option, as you suggested. (Of course, we could always just make the class final and privatize all the methods in order to avoid the maintenance headaches If one of the committers thinks this is a reasonable approach, I can try to implement it and post a patch this weekend. Let me know.
        Hide
        Daniel Rall added a comment -

        Geir, I'm totally in favor of that and have given my +1 on your vote.

        I'm looking at this issue as a new feature, where one has only an instance of a
        Class object and can't create an instance of the object it represents. This
        would be useful for some classes in the JDK which also have private constructors.

        Combining my suggestion with Jim Seach's code above would produce a backwards
        compatible solution for this issue.

        Irregardless of what happens with this issue, I am firm on StringUtils having a
        public ctor.

        Show
        Daniel Rall added a comment - Geir, I'm totally in favor of that and have given my +1 on your vote. I'm looking at this issue as a new feature, where one has only an instance of a Class object and can't create an instance of the object it represents. This would be useful for some classes in the JDK which also have private constructors. Combining my suggestion with Jim Seach's code above would produce a backwards compatible solution for this issue. Irregardless of what happens with this issue, I am firm on StringUtils having a public ctor.
        Hide
        Geir Magnusson Jr added a comment -

        or they could put the CTOR back

        Show
        Geir Magnusson Jr added a comment - or they could put the CTOR back
        Hide
        Daniel Rall added a comment -

        Alternately, the Invoker suggestion I made on the Commons dev list could be
        used. It may be trading one special case for another in the
        uberspector/introspector, but it preserves backwards compatibility and could be
        handled as a configuration option.

        http://archives.apache.org/eyebrowse/ReadMsg?listName=commons-dev@jakarta.apache.org&msgId=443946

        Show
        Daniel Rall added a comment - Alternately, the Invoker suggestion I made on the Commons dev list could be used. It may be trading one special case for another in the uberspector/introspector, but it preserves backwards compatibility and could be handled as a configuration option. http://archives.apache.org/eyebrowse/ReadMsg?listName=commons-dev@jakarta.apache.org&msgId=443946
        Hide
        Claude Brisson added a comment -

        Why not first look in the methods of java.lang.Class and then, only if not
        found, look into the static methods of the targeted class
        ?

        A static tool is not meant to contain methods like toString or newInstance...

        (still some forbidden names among the possibly usefull ones for your static
        methods, like 'getResourceAsStream' since it exists in java.lang.Class, but at
        least it
        is backward compatible)

        CloD

        Show
        Claude Brisson added a comment - Why not first look in the methods of java.lang.Class and then, only if not found, look into the static methods of the targeted class ? A static tool is not meant to contain methods like toString or newInstance... (still some forbidden names among the possibly usefull ones for your static methods, like 'getResourceAsStream' since it exists in java.lang.Class, but at least it is backward compatible) CloD
        Hide
        Jim Seach added a comment -

        The problem with my previous suggestion is that it is not 100% backwards
        compatible. If someone has a template that calls methods of a java.lang.Class
        object it will no longer work, unless a utility class is created to call
        methods of java.lang.Class.

        Show
        Jim Seach added a comment - The problem with my previous suggestion is that it is not 100% backwards compatible. If someone has a template that calls methods of a java.lang.Class object it will no longer work, unless a utility class is created to call methods of java.lang.Class.
        Hide
        Jim Seach added a comment -

        I haven't tested this, but would changing UberspectImpl.getMethod() to
        something like this work:

        public VelMethod getMethod(Object obj, String methodName,
        Object[] args, Info i)
        throws Exception
        {
        if (obj == null)
        return null;

        Class c = obj.getClass();
        Method m = null;

        if (c == java.lang.Class)
        {
        m = introspector.getMethod(obj, methodName, args);
        if (!java.lang.reflect.Modifier.isStatic(m.getModifiers()))

        { m = null; }

        }
        else

        { m = introspector.getMethod(c, methodName, args); }

        return (m != null) ? new VelMethodImpl(m) : null;
        }

        Show
        Jim Seach added a comment - I haven't tested this, but would changing UberspectImpl.getMethod() to something like this work: public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) throws Exception { if (obj == null) return null; Class c = obj.getClass(); Method m = null; if (c == java.lang.Class) { m = introspector.getMethod(obj, methodName, args); if (!java.lang.reflect.Modifier.isStatic(m.getModifiers())) { m = null; } } else { m = introspector.getMethod(c, methodName, args); } return (m != null) ? new VelMethodImpl(m) : null; }
        Hide
        Geir Magnusson Jr added a comment -

        Wow.

        I assume this is part of the organized resistance to public CTORs in commons-land?

        Why so complicated a solution? Couldn't something like :

        context.put("math", Math.class):

        and then if we find a Class in the context, we find static public methods and invoke them?
        I wonder if we can do that.

        Show
        Geir Magnusson Jr added a comment - Wow. I assume this is part of the organized resistance to public CTORs in commons-land? Why so complicated a solution? Couldn't something like : context.put("math", Math.class): and then if we find a Class in the context, we find static public methods and invoke them? I wonder if we can do that.
        Juozas Baliuka created issue -

          People

          • Assignee:
            Unassigned
            Reporter:
            Juozas Baliuka
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development