This page is work in progress. It will document the internals of groovy, the ideas and the techniques used so other developers may it have more easy to contribute to groovy in the future.
When are two classes the same? If the name is equal and if the class loader is equal. This means you can have multiple versions of the same class if you load the class through different class loaders. Of course this versions don't really have to be the same. This also means if you have code like
may fail because cls is not Foo. This also means calling a method like
with an Foo does not mean that "f(Foo) called" will be printed! This is no secret, you will find it in the language specification.
There are small conventions about how to do class loading in Java.
- always return the same class for the same name
- use your cache before asking a parent loader
- no parent loader doesn't mean the system loader
- ask your parent loader before trying ot load a class by yourself
Most of the loaders in groovy are violating one or more of these rules. I will exlpain why and how in the next sections
First let us start with the RootLoader. This one used used to start the console programs and is something like an advanced java launcher. When using Groovy embedded this loader is usually not used. The RootLoader sees itself as a root in a class loader tree. Against the convention to ask the parent before loading a class this laoder tries to load it by itself first. Only when this fails the parent is asked. They parent is usually the system class loader then. This step was needed because of many problems with multiple versions of libs, causing multiple classes of the same name where they are not expected, or classes loaded from the wrong libs. Because of that reason this loader may also be used in ant or maven to avoid clashes with the classes used by ant.
If you assign a value to a typed variable in Groovy, then this assignment will include an implicit cast. A Declaration counts as assignment in that sense as well.
String x = 8
is in fact
String x = (String) 8
Note: there is nothing to be done but boxing, if the cast is to Object, since everything in Groovy is an Object. The cast is realized in Groovy by writing ScriptByteCodeAdapter.castToType(8, String.class), which in turn will call DefaultTypeTransformation.castToType(Object, Class).
The following rules apply in the order given.
Casting rules for identity:
- value null stays null
- a cast to Object gives the value itself
- if the value class is equal the casting class, nothing is changed
- if the value class is a subclass of the casting class, nothing is changed
195 if (ReflectionCache.isArray(type))
These cases will cause a new value being created from the old one if the cast type is a Collection:
- a cast to HashSet will create a new HashSet using the HashSet(Collection) constructor
- if the value is an array and the we cast we try to invoke first the parameterless constructor of the cast type, to then add each element of the array using add(Object). Should there be no such constructor or its invocation fail, we fail with a GroovyCastException
- a cast to String will invoke toString() on the value
- a cast to char or Character will invoke the char casting rules
- a cast to Boolean or boolean will invoke the rules for Groovy Truth, see paragraph later
- a cast to Class we will use the toString() representation of value to execute Class.forName with that. The ClassLoader used for this will always be the loader for DefaultTypeTransformation.class.
- if we cast to a Number or primtive number type we will try to convert value to a number and then use one of the methods to create the kind of value we actually need. Should value not be a number already, but a Character, we use the charValue() for further steps. In case of a GString or String, we use the value of the one element String, if existing. Should it not exist, we fail with a GroovyCastExpression. If value is neither String, GString, Character or a Number, we fail with a GroovyCastExpression. A resulting Double with positive or negative infinity is only allowed, if the original value is already a double value (this may for example fail for the case of a BigDecimal, that does not fit into a Double). If not we fail with a GroovyCastException. BigDecimal and Biginteger are created by the doubleValue() in case we cast from a float or double. In the other cases we use the toString() representation.
- if we cast a String or GString value to an Enum type, we call Enum#valueOf to get an enum value based on the given string representation of that enum value
- if the value is an Collection, Map or Object, we try to invoke the cast type constructor. In case of a Object or Collect, the elements are used for the arguments. A Map will be given as single argument.
- if we reach this point, the cast fails with a GroovyCastException
- if the value is a Char, nothing is to be done
- if the value is a Number, we use the int value to create a char
- otherwise we execute toString() to get a String and if that String is a one element String, we will use that element to create the char. Is the String longer we fail with a GroovyCastException
The implementation of this is determined by calling the asBoolean method of the value. It will realize those cases:
- if value is null: false
- empty Collections, arrays, CharSequence (contains empty String): false
- boolean false: false
- numbers equaling to 0: false
- the char0 : false
- Enumeration that has no more elements: false
- Matcher in which the find method returns false: false
- in the other cases we return true