groovy
  1. groovy
  2. GROOVY-3451

Script returns "interface groovy.lang.Category" when evaluating "Category"

    Details

    • Type: Bug Bug
    • Status: Closed Closed
    • Priority: Major Major
    • Resolution: Won't Fix
    • Affects Version/s: 1.6
    • Fix Version/s: None
    • Component/s: class generator
    • Labels:
      None
    • Environment:
      XP or linux. Reproduces with a simple java program using embeddable groovy.
    • Testcase included:
      yes
    • Number of attachments :
      0

      Description

      If a script is simply "Category" where the binding has capability to resolve a variable of that name, Groovy returns "interface groovy.lang.Category" instead of the value of the variable "Category" from the given binding object. Here's a java code that reproduces this issue:

      import groovy.lang.Binding;
      import groovy.lang.GroovyShell;
      import groovy.lang.Script;
      
      public class testCategory {
      
          public static void main(String[] args) {
              Binding binding = new Binding() {
                  public Object getVariable(String key) {
                      if (key.equals("Category")) {
                          return new Long(210);
                      }
                      return super.getVariable(key);
                  }
              };
              GroovyShell shell =
                  new GroovyShell(Thread.currentThread().getContextClassLoader());
              String expr = "Category";
              Script scr = shell.parse(expr);
              System.out.println(expr);
              scr.setBinding(binding);
              Object value = scr.run();
              System.out.println(value);
          }
      }
      

      The output of the above code is expected to be:
      Category
      210

      instead, what we get is:
      Category
      interface groovy.lang.Category

        Activity

        Hide
        blackdrag blackdrag added a comment -

        since in Groovy class names and variables share a common namespace and since the tight integration with the VM requires us to know class names at compile time, we have to define rules when a vanilla name is a class and when not. So we defined that at compile time a vanilla name that does not refer to a local variable or field is first looked up as a class and if that fails the name is seen as dynamic property. Variables in the binding are here to be seen as dynamic properties. So if the class is preferred over the property it is no error. Now in case of a script there is an alternative syntax you can use. You could do this.Category, binding.Category or this.binding.Category. In case of classes there is no alternative syntax, so changing the static lookup order does not make sense.

        Show
        blackdrag blackdrag added a comment - since in Groovy class names and variables share a common namespace and since the tight integration with the VM requires us to know class names at compile time, we have to define rules when a vanilla name is a class and when not. So we defined that at compile time a vanilla name that does not refer to a local variable or field is first looked up as a class and if that fails the name is seen as dynamic property. Variables in the binding are here to be seen as dynamic properties. So if the class is preferred over the property it is no error. Now in case of a script there is an alternative syntax you can use. You could do this.Category, binding.Category or this.binding.Category. In case of classes there is no alternative syntax, so changing the static lookup order does not make sense.
        Hide
        S Vinayaka added a comment -

        So to make it easier for groovy framework implementation, the burden of name resolution is being left to script-writer's minds?
        This does not make sense from a script writer's perspective. Isn't there some way to qualify that the script needs the 'groovy class' instead of a variable.

        Also it'd be helpful if you could post the list of such groovy 'reserved' names like this.

        Thanks.

        Show
        S Vinayaka added a comment - So to make it easier for groovy framework implementation, the burden of name resolution is being left to script-writer's minds? This does not make sense from a script writer's perspective. Isn't there some way to qualify that the script needs the 'groovy class' instead of a variable. Also it'd be helpful if you could post the list of such groovy 'reserved' names like this. Thanks.
        Hide
        Roshan Dawrani added a comment -

        It's no use of reserved words.

        If the un-qualified variable name that you are using (that is not referring to any local variable or field) in your script clashes with name of any class known to the class-loader, you will have this problem - because class lookup is taking preference over lookup in binding.

        Show
        Roshan Dawrani added a comment - It's no use of reserved words. If the un-qualified variable name that you are using (that is not referring to any local variable or field) in your script clashes with name of any class known to the class-loader, you will have this problem - because class lookup is taking preference over lookup in binding.
        Hide
        blackdrag blackdrag added a comment -

        Roshan is right. Any unqualified name that shows up in any import with .* or import by name, which does not refer to a local variable does have this potential problem. Java has the convention that variables are started in lower case and classes in upper case. If you would follow that convention, then there would be no name clash. You have your reasons to do it like you do I suspect.

        Theoretically there is a way to resolve the issue to your favor. And that is that the compiler does look for the class, but does not fix the meaning of the name. Instead at runtime the lookup is done and if that fails the class is loaded. But that would make Groovy shameless slow and things like property missing would be flooded with requests.

        Isn't there some way to qualify that the script needs the 'groovy class' instead of a variable.

        You get the class, what you want is the variable defined through the invisible binding.

        Show
        blackdrag blackdrag added a comment - Roshan is right. Any unqualified name that shows up in any import with .* or import by name, which does not refer to a local variable does have this potential problem. Java has the convention that variables are started in lower case and classes in upper case. If you would follow that convention, then there would be no name clash. You have your reasons to do it like you do I suspect. Theoretically there is a way to resolve the issue to your favor. And that is that the compiler does look for the class, but does not fix the meaning of the name. Instead at runtime the lookup is done and if that fails the class is loaded. But that would make Groovy shameless slow and things like property missing would be flooded with requests. Isn't there some way to qualify that the script needs the 'groovy class' instead of a variable. You get the class, what you want is the variable defined through the invisible binding.
        Hide
        S Vinayaka added a comment -

        In both replies above, the key issue a script writer is facing is that there are no 'imports' or any other declerations in the script itself. Where did the class 'Category' come from if the script itself did not import it....It seems Groovy compiler is injecting classes into the script and it'd be good to document which of these Groovy 'names' are implied unless declared otherwise for script writers to be aware of such injection.

        Show
        S Vinayaka added a comment - In both replies above, the key issue a script writer is facing is that there are no 'imports' or any other declerations in the script itself. Where did the class 'Category' come from if the script itself did not import it....It seems Groovy compiler is injecting classes into the script and it'd be good to document which of these Groovy 'names' are implied unless declared otherwise for script writers to be aware of such injection.
        Hide
        Roshan Dawrani added a comment -

        The class Category comes from groovy.lang. Certain packages are imported by default in groovy (groovy.lang is one of them) as is java.lang.* in Java. And what packages are visible to groovy runtime by default is documented. You should try to look for that info in the documentation.

        So, if your unqualified name maps to any class visible to classloader (explicitly imported or belonging to packages that are auto-imported), then that name will get resolved to the class.

        Show
        Roshan Dawrani added a comment - The class Category comes from groovy.lang. Certain packages are imported by default in groovy (groovy.lang is one of them) as is java.lang.* in Java. And what packages are visible to groovy runtime by default is documented. You should try to look for that info in the documentation. So, if your unqualified name maps to any class visible to classloader (explicitly imported or belonging to packages that are auto-imported), then that name will get resolved to the class.
        Hide
        Steve Muench added a comment -

        We have products in production over a year that integrate groovy for business application development. This change of behavior breaks existing applications that happen to have persistent business object attributes with names like "Category", "Reference", "PropertyValue", "Sequence", and several others.

        At a very minimum, Groovy integrators should be able to control how names are resolved in the script so that we can maintain upward compatibility with all of the production apps that will now run into this issue.

        Show
        Steve Muench added a comment - We have products in production over a year that integrate groovy for business application development. This change of behavior breaks existing applications that happen to have persistent business object attributes with names like "Category", "Reference", "PropertyValue", "Sequence", and several others. At a very minimum, Groovy integrators should be able to control how names are resolved in the script so that we can maintain upward compatibility with all of the production apps that will now run into this issue.
        Hide
        blackdrag blackdrag added a comment -

        you see, you would get that problem even in Java if variables and classes would share a name space and if you compile from source each time. But well, it is still possible that something happens in Java, for example if a method gets overloaded and you call suddenly a different method than before causing a breaking change. The solution for this in Java is to compile not against the new Java, but the old Java, meaning cross compilation.

        Now for Groovy this is much more difficult. But you know, there is a class loader that you give in to GroovyShell, GroovyClassLoader or CompilationUnit (whatever you use to compile at runtime). in that loader you could black or white list classes from groovy.lang and groovy.util. Or you could attach a phase operation to the compiler (using CompilationUnit or GroovyClassLoader) to transform ClassExpressions into VariableExpressions.

        Is one of these ways suitable for you?

        Show
        blackdrag blackdrag added a comment - you see, you would get that problem even in Java if variables and classes would share a name space and if you compile from source each time. But well, it is still possible that something happens in Java, for example if a method gets overloaded and you call suddenly a different method than before causing a breaking change. The solution for this in Java is to compile not against the new Java, but the old Java, meaning cross compilation. Now for Groovy this is much more difficult. But you know, there is a class loader that you give in to GroovyShell, GroovyClassLoader or CompilationUnit (whatever you use to compile at runtime). in that loader you could black or white list classes from groovy.lang and groovy.util. Or you could attach a phase operation to the compiler (using CompilationUnit or GroovyClassLoader) to transform ClassExpressions into VariableExpressions. Is one of these ways suitable for you?
        Hide
        Steve Muench added a comment -

        Is the black/white listing in the classloader we pass in a Groovy feature or a classloader feature? Can you suggest how our example code above would need to change to adopt your black/white list suggestion as a workaround?

        Thanks.

        Show
        Steve Muench added a comment - Is the black/white listing in the classloader we pass in a Groovy feature or a classloader feature? Can you suggest how our example code above would need to change to adopt your black/white list suggestion as a workaround? Thanks.
        Hide
        blackdrag blackdrag added a comment - - edited

        Well, instead of feeding in the context loader you do

        GroovyClassLoader gcl = new GroovyClassLoader(contextLoader) {
          public Class loadClass(String name, boolean resolve) {
            if (name in blackllist) throw new ClassNotFoundException(...)
            return super.loadClass(name,resolve)
          }
        }
        

        It does not have to be a GroovyClassLoader, it can be any ClassLoader. anyway, you use this loader and give it to the shell, all done.

        Show
        blackdrag blackdrag added a comment - - edited Well, instead of feeding in the context loader you do GroovyClassLoader gcl = new GroovyClassLoader(contextLoader) { public Class loadClass( String name, boolean resolve) { if (name in blackllist) throw new ClassNotFoundException(...) return super .loadClass(name,resolve) } } It does not have to be a GroovyClassLoader, it can be any ClassLoader. anyway, you use this loader and give it to the shell, all done.
        Hide
        Steve Muench added a comment -

        Thanks. Consider a simple example where "groovy.lang.Category" and "java.lang.Long" were in the blacklist. Would this idea above just stop the default resolution of "groovy.lang.Category" and "java.lang.Long" before the script client gets a chance to resolve an unqualified "Category" or "Long" reference, or would it cripple the runtime from every loading/using (even a fully-qualified) instance of groovy.lang.Category or java.lang.Long?

        Show
        Steve Muench added a comment - Thanks. Consider a simple example where "groovy.lang.Category" and "java.lang.Long" were in the blacklist. Would this idea above just stop the default resolution of "groovy.lang.Category" and "java.lang.Long" before the script client gets a chance to resolve an unqualified "Category" or "Long" reference, or would it cripple the runtime from every loading/using (even a fully-qualified) instance of groovy.lang.Category or java.lang.Long?
        Hide
        blackdrag blackdrag added a comment -

        It works if you don't use these classes in your code. If you blacklist java.util.Category, then you must not use it in your code or else you are screwed.

        Show
        blackdrag blackdrag added a comment - It works if you don't use these classes in your code. If you blacklist java.util.Category, then you must not use it in your code or else you are screwed.

          People

          • Assignee:
            blackdrag blackdrag
            Reporter:
            S Vinayaka
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: