SLING-7085 - Reduce code duplication

* exported org.apache.sling.scripting.sightly.compiler.util.ObjectModel
* delegated calls from org.apache.sling.scripting.sightly.render.AbstractRuntimeObjectModel to ObjectModel

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1806167 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/BinaryOperator.java b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/BinaryOperator.java
index c0ab9b6..d689093 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/BinaryOperator.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/BinaryOperator.java
@@ -19,7 +19,7 @@
 package org.apache.sling.scripting.sightly.compiler.expression.nodes;
 
 import org.apache.sling.scripting.sightly.compiler.SightlyCompilerException;
-import org.apache.sling.scripting.sightly.impl.compiler.CompileTimeObjectModel;
+import org.apache.sling.scripting.sightly.compiler.util.ObjectModel;
 
 /**
  * Binary operators used in expressions.
@@ -31,7 +31,7 @@
     AND {
         @Override
         public Object eval(Object left, Object right) {
-            return (CompileTimeObjectModel.toBoolean(left)) ? right : left;
+            return (ObjectModel.toBoolean(left)) ? right : left;
         }
     },
     /**
@@ -40,7 +40,7 @@
     OR {
         @Override
         public Object eval(Object left, Object right) {
-            return (CompileTimeObjectModel.toBoolean(left)) ? left : right;
+            return (ObjectModel.toBoolean(left)) ? left : right;
         }
     },
     /**
@@ -49,7 +49,7 @@
     CONCATENATE {
         @Override
         public Object eval(Object left, Object right) {
-            return CompileTimeObjectModel.toString(left).concat(CompileTimeObjectModel.toString(right));
+            return ObjectModel.toString(left).concat(ObjectModel.toString(right));
         }
     },
     /**
@@ -131,7 +131,7 @@
     ADD {
         @Override
         public Object eval(Object left, Object right) {
-            return adjust(CompileTimeObjectModel.toNumber(left).doubleValue() + CompileTimeObjectModel.toNumber(right).doubleValue());
+            return adjust(ObjectModel.toNumber(left).doubleValue() + ObjectModel.toNumber(right).doubleValue());
         }
     },
 
@@ -141,7 +141,7 @@
     SUB {
         @Override
         public Object eval(Object left, Object right) {
-            return adjust(CompileTimeObjectModel.toNumber(left).doubleValue() - CompileTimeObjectModel.toNumber(right).doubleValue());
+            return adjust(ObjectModel.toNumber(left).doubleValue() - ObjectModel.toNumber(right).doubleValue());
         }
     },
     /**
@@ -150,7 +150,7 @@
     MUL {
         @Override
         public Object eval(Object left, Object right) {
-            return adjust(CompileTimeObjectModel.toNumber(left).doubleValue() * CompileTimeObjectModel.toNumber(right).doubleValue());
+            return adjust(ObjectModel.toNumber(left).doubleValue() * ObjectModel.toNumber(right).doubleValue());
         }
     },
     /**
@@ -159,7 +159,7 @@
     DIV {
         @Override
         public Object eval(Object left, Object right) {
-            return adjust(CompileTimeObjectModel.toNumber(left).doubleValue() / CompileTimeObjectModel.toNumber(right).doubleValue());
+            return adjust(ObjectModel.toNumber(left).doubleValue() / ObjectModel.toNumber(right).doubleValue());
         }
     },
     /**
@@ -168,7 +168,7 @@
     I_DIV {
         @Override
         public Object eval(Object left, Object right) {
-            return CompileTimeObjectModel.toNumber(left).intValue() / CompileTimeObjectModel.toNumber(right).intValue();
+            return ObjectModel.toNumber(left).intValue() / ObjectModel.toNumber(right).intValue();
         }
     },
 
@@ -178,8 +178,8 @@
     REM {
         @Override
         public Object eval(Object left, Object right) {
-            return adjust(CompileTimeObjectModel.toNumber(left).intValue()
-                    % CompileTimeObjectModel.toNumber(right).intValue());
+            return adjust(ObjectModel.toNumber(left).intValue()
+                    % ObjectModel.toNumber(right).intValue());
         }
 
     };
diff --git a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/UnaryOperator.java b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/UnaryOperator.java
index 98664ab..61ea5b4 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/UnaryOperator.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/UnaryOperator.java
@@ -19,7 +19,7 @@
 package org.apache.sling.scripting.sightly.compiler.expression.nodes;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.scripting.sightly.impl.compiler.CompileTimeObjectModel;
+import org.apache.sling.scripting.sightly.compiler.util.ObjectModel;
 
 /**
  * Unary operators used in expressions.
@@ -30,7 +30,7 @@
     NOT {
         @Override
         public Object eval(Object operand) {
-            return !CompileTimeObjectModel.toBoolean(operand);
+            return !ObjectModel.toBoolean(operand);
         }
     },
 
@@ -38,7 +38,7 @@
     IS_WHITESPACE  {
         @Override
         public Object eval(Object operand) {
-            return StringUtils.isWhitespace(CompileTimeObjectModel.toString(operand));
+            return StringUtils.isWhitespace(ObjectModel.toString(operand));
         }
     },
 
@@ -48,7 +48,7 @@
     LENGTH {
         @Override
         public Object eval(Object operand) {
-            return CompileTimeObjectModel.toCollection(operand).size();
+            return ObjectModel.toCollection(operand).size();
         }
     };
 
diff --git a/src/main/java/org/apache/sling/scripting/sightly/compiler/util/ObjectModel.java b/src/main/java/org/apache/sling/scripting/sightly/compiler/util/ObjectModel.java
new file mode 100644
index 0000000..2b26bee
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/compiler/util/ObjectModel.java
@@ -0,0 +1,443 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.compiler.util;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@code ObjectModel} class provides various static models for object conversion and object property resolution.
+ */
+public final class ObjectModel {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ObjectModel.class);
+
+    /**
+     * A {@link Set} that stores all the supported primitive classes.
+     */
+    public static final Set<Class<?>> PRIMITIVE_CLASSES;
+
+    static {
+        Set<Class<?>> primitivesBuilder = new HashSet<>();
+        primitivesBuilder.add(Boolean.class);
+        primitivesBuilder.add(Boolean.class);
+        primitivesBuilder.add(Character.class);
+        primitivesBuilder.add(Byte.class);
+        primitivesBuilder.add(Short.class);
+        primitivesBuilder.add(Integer.class);
+        primitivesBuilder.add(Long.class);
+        primitivesBuilder.add(Float.class);
+        primitivesBuilder.add(Double.class);
+        primitivesBuilder.add(Void.class);
+        PRIMITIVE_CLASSES = Collections.unmodifiableSet(primitivesBuilder);
+    }
+
+
+    private static final String TO_STRING_METHOD = "toString";
+
+    private ObjectModel() {}
+
+    /**
+     * Checks if the provided {@code object} is an instance of a primitive class.
+     *
+     * @param object the {@code Object} to check
+     * @return {@code true} if the {@code object} is a primitive, {@code false} otherwise
+     */
+    public static boolean isPrimitive(Object object) {
+        return PRIMITIVE_CLASSES.contains(object.getClass());
+    }
+
+    /**
+     * Given the {@code target} object, this method attempts to resolve and return the value of the passed {@code property}.
+     *
+     * <p>
+     *     The property can be either an index or a name:
+     *     <ul>
+     *         <li>index: the property is considered an index if its value is an integer number and in this case the {@code target}
+     *         will be assumed to be either an array or it will be converted to a {@link Collection}; a fallback to {@link Map} will be
+     *         made in case the previous two attempts failed
+     *         </li>
+     *         <li>name: the {@code property} will be converted to a {@link String} (see {@link #toString(Object)}); the {@code target}
+     *         will be assumed to be either a {@link Map} or an object; if the {@link Map} attempt fails, the {@code property} will be
+     *         used to check if the {@code target} has a publicly accessible field with this name or a publicly accessible method with no
+     *         parameters with this name or a combination of the "get" or "is" prefixes plus the capitalised name (see
+     *         {@link #invokeBeanMethod(Object, String)})</li>
+     *     </ul>
+     * </p>
+     * @param target the target object
+     * @param property the property to be resolved
+     * @return the value of the property or {@code null}
+     */
+    public static Object resolveProperty(Object target, Object property) {
+        if (target == null || property == null) {
+            return null;
+        }
+        Object resolved = null;
+        if (property instanceof Number) {
+            resolved = getIndex(target, ((Number) property).intValue());
+        }
+        if (resolved == null) {
+            String propertyName = toString(property);
+            if (StringUtils.isNotEmpty(propertyName)) {
+                if (target instanceof Map) {
+                    resolved = ((Map) target).get(propertyName);
+                }
+                if (resolved == null) {
+                    resolved = getField(target, propertyName);
+                }
+                if (resolved == null) {
+                    resolved = invokeBeanMethod(target, propertyName);
+                }
+            }
+        }
+        return resolved;
+    }
+
+    /**
+     * Converts the given {@code object} to a boolean value, applying the following rules:
+     *
+     * <ul>
+     *     <li>if the {@code object} is {@code null} the returned value is {@code false}</li>
+     *     <li>if the {@code object} is a {@link Number} the method will return {@code false} only if the number's value is 0</li>
+     *     <li>if the {@link String} representation of the {@code object} is equal irrespective of its casing to "true", the method will
+     *     return {@code true}</li>
+     *     <li>if the {@code object} is a {@link Collection} or a {@link Map}, the method will return {@code true} only if the collection /
+     *     map is not empty</li>
+     *     <li>if the object is an array, the method will return {@code true} only if the array is not empty</li>
+     * </ul>
+     *
+     * @param object the target object
+     * @return the boolean representation of the {@code object} according to the conversion rules
+     */
+    public static boolean toBoolean(Object object) {
+        if (object == null) {
+            return false;
+        }
+
+        if (object instanceof Number) {
+            Number number = (Number) object;
+            return !(number.doubleValue() == 0.0);
+        }
+
+        String s = object.toString().trim();
+        if ("".equals(s)) {
+            return false;
+        } else if ("true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) {
+            return Boolean.parseBoolean(s);
+        }
+
+        if (object instanceof Collection) {
+            return ((Collection) object).size() > 0;
+        }
+
+        if (object instanceof Map) {
+            return ((Map) object).size() > 0;
+        }
+
+        if (object instanceof Iterable<?>) {
+            return ((Iterable<?>) object).iterator().hasNext();
+        }
+
+        if (object instanceof Iterator<?>) {
+            return ((Iterator<?>) object).hasNext();
+        }
+
+        return !(object instanceof Object[]) || ((Object[]) object).length > 0;
+    }
+
+    /**
+     * Coerces the passed {@code object} to a numeric value. If the passed value is a {@link String} the conversion rules are those of
+     * {@link NumberUtils#createNumber(String)}.
+     *
+     * @param object the target object
+     * @return the numeric representation if one can be determined or {@code null}
+     * @see NumberUtils#createNumber(String)
+     */
+    public static Number toNumber(Object object) {
+        if (object == null) {
+            return null;
+        }
+        if (object instanceof Number) {
+            return (Number) object;
+        }
+        String stringValue = toString(object);
+        try {
+            return NumberUtils.createNumber(stringValue);
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Converts the passed {@code object} to a {@link String}. The following rules apply:
+     *
+     * <ul>
+     *     <li>if the {@code object} is {@code null} an empty string will be returned</li>
+     *     <li>if the {@code object} is an instance of a {@link String} the object itself will be returned</li>
+     *     <li>if the object is a primitive (see {@link #isPrimitive(Object)}), its {@link String} representation will be returned</li>
+     *     <li>if the object is an {@link Enum} its name will be returned (see {@link Enum#name()})</li>
+     *     <li>otherwise an attempt to convert the object to a {@link Collection} will be made and then the output of
+     *     {@link #collectionToString(Collection)} will be returned</li>
+     * </ul>
+     *
+     * @param object the target object
+     * @return the string representation of the object or an empty string
+     */
+    public static String toString(Object object) {
+        String output = "";
+        if (object != null) {
+            if (object instanceof String) {
+                output = (String) object;
+            } else if (isPrimitive(object)) {
+                output = object.toString();
+            } else if (object instanceof Enum) {
+                return ((Enum) object).name();
+            } else {
+                Collection<?> col = toCollection(object);
+                output = collectionToString(col);
+            }
+        }
+        return output;
+    }
+
+    /**
+     * Forces the conversion of the passed {@code object} to a collection, according to the following rules:
+     *
+     * <ul>
+     *     <li>if the {@code object} is {@code null} an empty collection will be returned</li>
+     *     <li>if the {@code object} is an array a list transformation of the array will be returned</li>
+     *     <li>if the {@code object} is a {@link Collection} the object itself will be returned</li>
+     *     <li>if the {@code object} is an instance of a {@link Map} the map's key set will be returned (see {@link Map#keySet()})</li>
+     *     <li>if the {@code object} is an instance of an {@link Enumeration} a list transformation will be returned</li>
+     *     <li>if the {@code object} is an instance of an {@link Iterator} or {@link Iterable} the result of {@link #fromIterator(Iterator)}
+     *     will be returned</li>
+     *     <li>if the {@code object} is an instance of a {@link String} or {@link Number} a {@link Collection} containing only this
+     *     object will be returned</li>
+     *     <li>any other case not covered by the previous rules will result in an empty {@link Collection}</li>
+     * </ul>
+     *
+     * @param object the target object
+     * @return the collection representation of the object
+     */
+    public static Collection<Object> toCollection(Object object) {
+        if (object == null) {
+            return Collections.emptyList();
+        }
+        if (object instanceof Object[]) {
+            return Arrays.asList((Object[]) object);
+        }
+        if (object instanceof Collection) {
+            return (Collection<Object>) object;
+        }
+        if (object instanceof Map) {
+            return ((Map) object).keySet();
+        }
+        if (object instanceof Enumeration) {
+            return Collections.list((Enumeration<Object>) object);
+        }
+        if (object instanceof Iterator) {
+            return fromIterator((Iterator<Object>) object);
+        }
+        if (object instanceof Iterable) {
+            Iterable<Object> iterable = (Iterable<Object>) object;
+            return fromIterator(iterable.iterator());
+        }
+        if (object instanceof String || object instanceof Number) {
+            Collection<Object> list = new ArrayList<>();
+            list.add(object);
+            return list;
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Converts the passed {@code collection} to a comma separated values {@link String} representation.
+     *
+     * @param collection the collection to be converted to CSV
+     * @return the CSV; if the {@code collection} is empty then an empty string will be returned
+     */
+    public static String collectionToString(Collection<?> collection) {
+        StringBuilder builder = new StringBuilder();
+        String prefix = "";
+        for (Object o : collection) {
+            builder.append(prefix).append(toString(o));
+            prefix = ",";
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Given an {@code iterator}, this method will return a {@link Collection}.
+     *
+     * @param iterator the iterator to be transformed into a {@code collection}
+     * @return a collection with the iterator's elements
+     */
+    public static Collection<Object> fromIterator(Iterator<Object> iterator) {
+        ArrayList<Object> result = new ArrayList<>();
+        while (iterator.hasNext()) {
+            result.add(iterator.next());
+        }
+        return result;
+    }
+
+    /**
+     * Given an indexable {@code object} (i.e. an array or a collection), this method will return the value available at the {@code
+     * index} position.
+     *
+     * @param object the indexable object
+     * @param index  the index
+     * @return the value stored at the {@code index} or {@code null}
+     */
+    public static Object getIndex(Object object, int index) {
+        Class<?> cls = object.getClass();
+        if (cls.isArray() && index >= 0 && index < Array.getLength(object)) {
+            return Array.get(object, index);
+        }
+        Collection collection = toCollection(object);
+        if (collection instanceof List && index >= 0 && index < collection.size()) {
+            return ((List) collection).get(index);
+        }
+        return null;
+    }
+
+    /**
+     * Given an {@code object}, this method will return the value of the public field identified by {@code fieldName}.
+     *
+     * @param object    the target object
+     * @param fieldName the name of the field
+     * @return the value of the field or {@code null} if the field was not found
+     */
+    public static Object getField(Object object, String fieldName) {
+        Class<?> cls = object.getClass();
+        if (cls.isArray() && "length".equals(fieldName)) {
+            return Array.getLength(object);
+        }
+        try {
+            Field field = cls.getField(fieldName);
+            return field.get(object);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Given a bean {@code object}, this method will invoke the public method without parameters identified by {@code methodName} and
+     * return the invocation's result.
+     *
+     * @param object     the target object
+     * @param methodName the name of the public method without parameters to invoke
+     * @return the invocation's result or {@code null} if such a method cannot be found
+     */
+    public static Object invokeBeanMethod(Object object, String methodName) {
+        Class<?> cls = object.getClass();
+        Method method = findBeanMethod(cls, methodName);
+        if (method != null) {
+            try {
+                method = extractMethodInheritanceChain(cls, method);
+                return method.invoke(object);
+            } catch (Exception e) {
+                LOGGER.error("Cannot access method " + methodName + " on object " + object.toString(), e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Given a bean class and a base method name, this method will try to find a public method without parameters that is named:
+     * <ol>
+     *      <li>{@code baseName}</li>
+     *      <li>get + {@code BaseName}</li>
+     *      <li>is + {@code BaseName}</li>
+     * </ol>
+     *
+     * @param cls      the class into which to search for the method
+     * @param baseName the base method name
+     * @return a method that matches the criteria or {@code null}
+     */
+    public static Method findBeanMethod(Class<?> cls, String baseName) {
+        Method[] publicMethods = cls.getMethods();
+        String capitalized = StringUtils.capitalize(baseName);
+        for (Method method : publicMethods) {
+            if (method.getParameterTypes().length == 0) {
+                String methodName = method.getName();
+                if (baseName.equals(methodName) || ("get" + capitalized).equals(methodName) || ("is" + capitalized).equals(methodName)) {
+                    if (isMethodAllowed(method)) {
+                        return method;
+                    }
+                    break;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns {@code true} if the method is not one of the {@link Object}'s class declared methods, with the exception of
+     * {@link Object#toString()}.
+     *
+     * @param method the method to check
+     * @return {@code true} if the method is not one of the {@link Object}'s class declared methods, with the exception of
+     * {@link Object#toString()}, {@code false} otherwise
+     */
+    public static boolean isMethodAllowed(Method method) {
+        Class<?> declaringClass = method.getDeclaringClass();
+        return declaringClass != Object.class || TO_STRING_METHOD.equals(method.getName());
+    }
+
+    private static Method extractMethodInheritanceChain(Class type, Method method) throws NoSuchMethodException {
+        if (method == null || Modifier.isPublic(type.getModifiers())) {
+            return method;
+        }
+        Class[] interfaces = type.getInterfaces();
+        Method parentMethod;
+        for (Class<?> iface : interfaces) {
+            parentMethod = getClassMethod(iface, method);
+            if (parentMethod != null) {
+                return parentMethod;
+            }
+        }
+        return getClassMethod(type.getSuperclass(), method);
+    }
+
+    private static Method getClassMethod(Class<?> type, Method method) throws NoSuchMethodException {
+        Method parentMethod = type.getMethod(method.getName(), method.getParameterTypes());
+        parentMethod = extractMethodInheritanceChain(parentMethod.getDeclaringClass(), parentMethod);
+        if (parentMethod != null) {
+            return parentMethod;
+        }
+        return null;
+    }
+
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/compiler/util/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/compiler/util/package-info.java
index 3c6a718..8616845 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/compiler/util/package-info.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/compiler/util/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  ******************************************************************************/
 
-@Version("1.0.0")
+@Version("1.1.0")
 package org.apache.sling.scripting.sightly.compiler.util;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/CompileTimeObjectModel.java b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/CompileTimeObjectModel.java
deleted file mode 100644
index 4215504..0000000
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/CompileTimeObjectModel.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- ******************************************************************************/
-package org.apache.sling.scripting.sightly.impl.compiler;
-
-import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-public final class CompileTimeObjectModel {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(CompileTimeObjectModel.class);
-
-    /**
-     * A {@link Set} that stores all the supported primitive classes.
-     */
-    public static final Set<Class<?>> PRIMITIVE_CLASSES = Collections.unmodifiableSet(new HashSet<Class<?>>() {{
-        add(Boolean.class);
-        add(Boolean.class);
-        add(Character.class);
-        add(Byte.class);
-        add(Short.class);
-        add(Integer.class);
-        add(Long.class);
-        add(Float.class);
-        add(Double.class);
-        add(Void.class);
-    }});
-
-    public static final String TO_STRING_METHOD = "toString";
-
-    public static boolean isPrimitive(Object obj) {
-        return PRIMITIVE_CLASSES.contains(obj.getClass());
-    }
-
-    /**
-     * Resolve a property of a target object and return its value. The property can
-     * be either an index or a name
-     *
-     * @param target the target object
-     * @param property the property to be resolved
-     * @return the value of the property
-     */
-    public static Object resolveProperty(Object target, Object property) {
-        Object resolved;
-        if (property instanceof Number) {
-            resolved = getIndex(target, ((Number) property).intValue());
-        } else {
-            resolved = getProperty(target, property);
-        }
-        return resolved;
-    }
-
-    /**
-     * Convert the given object to a boolean value
-     *
-     * @param object the target object
-     * @return the boolean representation of that object
-     */
-    public static boolean toBoolean(Object object) {
-        return toBooleanInternal(object);
-    }
-
-    /**
-     * Coerce the object to a numeric value
-     *
-     * @param object - the target object
-     * @return - the numeric representation
-     */
-    public static Number toNumber(Object object) {
-        if (object instanceof Number) {
-            return (Number) object;
-        }
-        return 0;
-    }
-
-    /**
-     * Convert the given object to a string.
-     *
-     * @param target the target object
-     * @return the string representation of the object
-     */
-    public static String toString(Object target) {
-        return objectToString(target);
-    }
-
-    /**
-     * Force the conversion of the object to a collection
-     *
-     * @param object the target object
-     * @return the collection representation of the object
-     */
-    public static Collection<Object> toCollection(Object object) {
-        return obtainCollection(object);
-    }
-
-
-    private static String objectToString(Object obj) {
-        String output = "";
-        if (obj != null) {
-            if (obj instanceof String) {
-                output = (String) obj;
-            } else if (isPrimitive(obj)) {
-                output = obj.toString();
-            } else if (obj instanceof Enum) {
-                return ((Enum) obj).name();
-            } else {
-                Collection<?> col = obtainCollection(obj);
-                if (col != null) {
-                    output = collectionToString(col);
-                }
-            }
-        }
-        return output;
-    }
-
-    private static Object getProperty(Object target, Object propertyObj) {
-        String property = toString(propertyObj);
-        if (StringUtils.isEmpty(property)) {
-            throw new IllegalArgumentException("Invalid property name");
-        }
-        if (target == null) {
-            return null;
-        }
-        Object result = null;
-        if (target instanceof Map) {
-            result = getMapProperty((Map) target, property);
-        }
-        if (result == null) {
-            result = getObjectProperty(target, property);
-        }
-        return result;
-    }
-
-    @SuppressWarnings("unchecked")
-    private static Collection<Object> obtainCollection(Object obj) {
-        if (obj == null) {
-            return Collections.emptyList();
-        }
-        if (obj instanceof Object[]) {
-            return Arrays.asList((Object[]) obj);
-        }
-        if (obj instanceof Collection) {
-            return (Collection<Object>) obj;
-        }
-        if (obj instanceof Map) {
-            return ((Map) obj).keySet();
-        }
-        if (obj instanceof Enumeration) {
-            return Collections.list((Enumeration<Object>) obj);
-        }
-        if (obj instanceof Iterator) {
-            return fromIterator((Iterator<Object>) obj);
-        }
-        if (obj instanceof Iterable) {
-            Iterable iterable = (Iterable) obj;
-            return fromIterator(iterable.iterator());
-        }
-        if (obj instanceof String || obj instanceof Number) {
-            Collection list = new ArrayList();
-            list.add(obj);
-            return list;
-        }
-        return Collections.emptyList();
-    }
-
-    private static String collectionToString(Collection<?> col) {
-        StringBuilder builder = new StringBuilder();
-        String prefix = "";
-        for (Object o : col) {
-            builder.append(prefix).append(objectToString(o));
-            prefix = ",";
-        }
-        return builder.toString();
-    }
-
-    private static Collection<Object> fromIterator(Iterator<Object> iterator) {
-        ArrayList<Object> result = new ArrayList<>();
-        while (iterator.hasNext()) {
-            result.add(iterator.next());
-        }
-        return result;
-    }
-
-    private static boolean toBooleanInternal(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-
-        if (obj instanceof Number) {
-            Number number = (Number) obj;
-            //todo should we consider precision issues?
-            return !(number.doubleValue() == 0.0);
-        }
-
-        String s = obj.toString().trim();
-        if ("".equals(s)) {
-            return false;
-        } else if ("true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) {
-            return Boolean.parseBoolean(s);
-        }
-
-        if (obj instanceof Collection) {
-            return ((Collection) obj).size() > 0;
-        }
-
-        if (obj instanceof Map) {
-            return ((Map) obj).size() > 0;
-        }
-
-        if (obj instanceof Iterable<?>) {
-            return ((Iterable<?>) obj).iterator().hasNext();
-        }
-
-        if (obj instanceof Iterator<?>) {
-            return ((Iterator<?>) obj).hasNext();
-        }
-
-        return !(obj instanceof Object[]) || ((Object[]) obj).length > 0;
-    }
-
-    private static Object getIndex(Object obj, int index) {
-        if (obj instanceof Map) {
-            Map map = (Map) obj;
-            if (map.containsKey(index)) {
-                return map.get(index);
-            }
-        }
-        Collection collection = toCollection(obj);
-        if (collection instanceof List) {
-            return getIndexSafe((List) collection, index);
-        }
-        return null;
-    }
-
-    private static Object getIndexSafe(List list, int index) {
-        if (index < 0 || index >= list.size()) {
-            return null;
-        }
-        return list.get(index);
-    }
-
-    private static Object getMapProperty(Map map, String property) {
-        return map.get(property);
-    }
-
-    private static Object getObjectProperty(Object obj, String property) {
-        Object result = getObjectNoArgMethod(obj, property);
-        if (result == null) {
-            result = getField(obj, property);
-        }
-        return result;
-    }
-
-    private static Object getField(Object obj, String property) {
-        Class<?> cls = obj.getClass();
-        if (cls.isArray() && "length".equals(property)) {
-            return Array.getLength(obj);
-        }
-        try {
-            Field field = cls.getDeclaredField(property);
-            return field.get(obj);
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    private static Object getObjectNoArgMethod(Object obj, String property) {
-        Class<?> cls = obj.getClass();
-        Method method = findMethod(cls, property);
-        if (method != null) {
-            method = extractMethodInheritanceChain(cls, method);
-            try {
-                return method.invoke(obj);
-            } catch (Exception e) {
-                LOGGER.error("Cannot access method " + property + " on object " + obj.toString(), e);
-            }
-        }
-        return null;
-    }
-
-    private static Method findMethod(Class<?> cls, String baseName) {
-        Method[] publicMethods = cls.getMethods();
-        String capitalized = StringUtils.capitalize(baseName);
-        for (Method m : publicMethods) {
-            if (m.getParameterTypes().length == 0) {
-                String methodName = m.getName();
-                if (baseName.equals(methodName) || ("get" + capitalized).equals(methodName) || ("is" + capitalized).equals(methodName)) {
-                    // this method is good, check whether allowed
-                    if (isMethodAllowed(m)) {
-                        return m;
-                    }
-                    // method would match but is not allowed, abort
-                    break;
-                }
-            }
-        }
-        return null;
-    }
-
-    private static boolean isMethodAllowed(Method method) {
-        Class<?> declaringClass = method.getDeclaringClass();
-        //methods of the Object.class are forbidden (except toString, which is allowed)
-        return declaringClass != Object.class || TO_STRING_METHOD.equals(method.getName());
-    }
-
-    private static Method extractMethodInheritanceChain(Class type, Method m) {
-        if (m == null || Modifier.isPublic(type.getModifiers())) {
-            return m;
-        }
-        Class[] iFaces = type.getInterfaces();
-        Method mp;
-        for (Class<?> iFace : iFaces) {
-            mp = getClassMethod(iFace, m);
-            if (mp != null) {
-                return mp;
-            }
-        }
-        return getClassMethod(type.getSuperclass(), m);
-    }
-
-    private static Method getClassMethod(Class<?> clazz, Method m) {
-        Method mp;
-        try {
-            mp = clazz.getMethod(m.getName(), m.getParameterTypes());
-            mp = extractMethodInheritanceChain(mp.getDeclaringClass(), mp);
-            if (mp != null) {
-                return mp;
-            }
-        } catch (NoSuchMethodException e) {
-            // do nothing
-        }
-        return null;
-    }
-
-
-}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/optimization/DeadCodeRemoval.java b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/optimization/DeadCodeRemoval.java
index c2f62fe..f4601df 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/optimization/DeadCodeRemoval.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/optimization/DeadCodeRemoval.java
@@ -35,7 +35,7 @@
 import org.apache.sling.scripting.sightly.impl.compiler.util.stream.Streams;
 import org.apache.sling.scripting.sightly.impl.compiler.visitor.StatefulRangeIgnore;
 import org.apache.sling.scripting.sightly.impl.compiler.visitor.TrackingVisitor;
-import org.apache.sling.scripting.sightly.impl.compiler.CompileTimeObjectModel;
+import org.apache.sling.scripting.sightly.compiler.util.ObjectModel;
 
 /**
  * Removes code under conditionals which are proven to fail. It is probably a good idea to run this optimization after running
@@ -70,16 +70,16 @@
         Boolean truthValue = null;
         ExpressionNode node = variableBindingStart.getExpression();
         if (node instanceof StringConstant) {
-            truthValue = CompileTimeObjectModel.toBoolean(((StringConstant) node).getText());
+            truthValue = ObjectModel.toBoolean(((StringConstant) node).getText());
         }
         if (node instanceof BooleanConstant) {
             truthValue = ((BooleanConstant) node).getValue();
         }
         if (node instanceof NumericConstant) {
-            truthValue = CompileTimeObjectModel.toBoolean(((NumericConstant) node).getValue());
+            truthValue = ObjectModel.toBoolean(((NumericConstant) node).getValue());
         }
         if (node instanceof NullLiteral) {
-           truthValue = CompileTimeObjectModel.toBoolean(null);
+           truthValue = ObjectModel.toBoolean(null);
         }
         tracker.pushVariable(variableBindingStart.getVariableName(), truthValue);
         outStream.write(variableBindingStart);
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/optimization/reduce/ExpressionReducer.java b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/optimization/reduce/ExpressionReducer.java
index f6ce62b..a721aef 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/optimization/reduce/ExpressionReducer.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/optimization/reduce/ExpressionReducer.java
@@ -40,7 +40,7 @@
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.TernaryOperator;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.UnaryOperation;
 import org.apache.sling.scripting.sightly.compiler.util.VariableTracker;
-import org.apache.sling.scripting.sightly.impl.compiler.CompileTimeObjectModel;
+import org.apache.sling.scripting.sightly.compiler.util.ObjectModel;
 
 /**
  * Try to evaluate constant parts in expressions
@@ -87,7 +87,7 @@
                     property.getNode()));
         }
 
-        return EvalResult.constant(CompileTimeObjectModel.resolveProperty(
+        return EvalResult.constant(ObjectModel.resolveProperty(
                 target.getValue(), property.getValue()));
     }
 
@@ -147,7 +147,7 @@
                     ternaryOperator.getThenBranch(),
                     ternaryOperator.getElseBranch()));
         }
-        return (CompileTimeObjectModel.toBoolean(condition.getValue()))
+        return (ObjectModel.toBoolean(condition.getValue()))
                 ? eval(ternaryOperator.getThenBranch())
                 : eval(ternaryOperator.getElseBranch());
     }
diff --git a/src/test/java/org/apache/sling/scripting/sightly/impl/utils/CompileTimeObjectModelTest.java b/src/test/java/org/apache/sling/scripting/sightly/impl/utils/CompileTimeObjectModelTest.java
deleted file mode 100644
index 1b8febc..0000000
--- a/src/test/java/org/apache/sling/scripting/sightly/impl/utils/CompileTimeObjectModelTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ******************************************************************************/
-package org.apache.sling.scripting.sightly.impl.utils;
-
-import java.util.Collection;
-
-import org.apache.sling.scripting.sightly.impl.compiler.CompileTimeObjectModel;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-public class CompileTimeObjectModelTest {
-
-    @Test
-    public void testGetCollectionWithOneElement() {
-        String stringObject = "test";
-        Integer numberObject = 1;
-        Collection stringCollection = CompileTimeObjectModel.toCollection(stringObject);
-        assertTrue(stringCollection.size() == 1 && stringCollection.contains(stringObject));
-        Collection numberCollection = CompileTimeObjectModel.toCollection(numberObject);
-        assertTrue(numberCollection.size() == 1 && numberCollection.contains(numberObject));
-    }
-
-}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/testobjects/Person.java b/src/test/java/org/apache/sling/scripting/sightly/testobjects/Person.java
new file mode 100644
index 0000000..c1fa535
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/testobjects/Person.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.testobjects;
+
+public interface Person {
+
+    long CONSTANT = 1;
+
+    String getFirstName();
+
+    String getLastName();
+    
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/testobjects/internal/AbstractPerson.java b/src/test/java/org/apache/sling/scripting/sightly/testobjects/internal/AbstractPerson.java
new file mode 100644
index 0000000..39a5d05
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/testobjects/internal/AbstractPerson.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.testobjects.internal;
+
+import org.apache.sling.scripting.sightly.testobjects.Person;
+
+abstract class AbstractPerson implements Person {
+
+    private String firstName;
+    private String lastName;
+
+    public AbstractPerson(String firstName, String lastName) {
+        this.firstName = firstName;
+        this.lastName = lastName;
+    }
+
+    @Override
+    public String getFirstName() {
+        return firstName;
+    }
+
+    @Override
+    public String getLastName() {
+        return lastName;
+    }
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/testobjects/internal/Adult.java b/src/test/java/org/apache/sling/scripting/sightly/testobjects/internal/Adult.java
new file mode 100644
index 0000000..483f7d8
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/testobjects/internal/Adult.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.testobjects.internal;
+
+class Adult extends AbstractPerson {
+
+    public static final long TODAY = System.currentTimeMillis();
+
+    Adult(String firstName, String lastName) {
+        super(firstName, lastName);
+    }
+
+    public String getFullName() {
+        return getFirstName() + ", " + getLastName();
+    }
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/testobjects/internal/AdultFactory.java b/src/test/java/org/apache/sling/scripting/sightly/testobjects/internal/AdultFactory.java
new file mode 100644
index 0000000..71e9292
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/testobjects/internal/AdultFactory.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.testobjects.internal;
+
+public class AdultFactory {
+
+    public static Adult createAdult(String firstName, String lastName) {
+        return new Adult(firstName, lastName);
+    }
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/util/ObjectModelTest.java b/src/test/java/org/apache/sling/scripting/sightly/util/ObjectModelTest.java
new file mode 100644
index 0000000..52a6600
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/util/ObjectModelTest.java
@@ -0,0 +1,193 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.util;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.sling.scripting.sightly.compiler.expression.nodes.BinaryOperator;
+import org.apache.sling.scripting.sightly.compiler.util.ObjectModel;
+import org.apache.sling.scripting.sightly.testobjects.Person;
+import org.apache.sling.scripting.sightly.testobjects.internal.AdultFactory;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class ObjectModelTest {
+
+    @Test
+    public void testToBoolean() {
+        assertFalse(ObjectModel.toBoolean(null));
+        assertFalse(ObjectModel.toBoolean(0));
+        assertTrue(ObjectModel.toBoolean(123456));
+        assertFalse(ObjectModel.toBoolean(""));
+        assertFalse(ObjectModel.toBoolean("FalSe"));
+        assertFalse(ObjectModel.toBoolean("false"));
+        assertFalse(ObjectModel.toBoolean("FALSE"));
+        assertTrue(ObjectModel.toBoolean("true"));
+        assertTrue(ObjectModel.toBoolean("TRUE"));
+        assertTrue(ObjectModel.toBoolean("TrUE"));
+        Integer[] testArray = new Integer[] {1, 2, 3};
+        List testList = Arrays.asList(testArray);
+        assertTrue(ObjectModel.toBoolean(testArray));
+        assertFalse(ObjectModel.toBoolean(new Integer[]{}));
+        assertTrue(ObjectModel.toBoolean(testList));
+        assertFalse(ObjectModel.toBoolean(Collections.emptyList()));
+        Map<String, Integer> map = new HashMap<String, Integer>() {{
+            put("one", 1);
+            put("two", 2);
+        }};
+        assertTrue(ObjectModel.toBoolean(map));
+        assertFalse(ObjectModel.toBoolean(Collections.EMPTY_MAP));
+        assertTrue(ObjectModel.toBoolean(testList.iterator()));
+        assertFalse(ObjectModel.toBoolean(Collections.EMPTY_LIST.iterator()));
+        assertTrue(ObjectModel.toBoolean(new Bag<>(testArray)));
+        assertFalse(ObjectModel.toBoolean(new Bag<>(new Integer[]{})));
+        assertTrue(ObjectModel.toBoolean(new Date()));
+    }
+
+    @Test
+    public void testToNumber() {
+        assertEquals(1, ObjectModel.toNumber(1));
+        assertEquals(1, ObjectModel.toNumber("1"));
+        assertNull(ObjectModel.toNumber(null));
+        assertNull(ObjectModel.toNumber("1-2"));
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("", ObjectModel.toString(null));
+        assertEquals("1", ObjectModel.toString("1"));
+        assertEquals("1", ObjectModel.toString(1));
+        assertEquals("ADD", ObjectModel.toString(BinaryOperator.ADD));
+        Integer[] testArray = new Integer[] {1, 2, 3};
+        List testList = Arrays.asList(testArray);
+        assertEquals("1,2,3", ObjectModel.toString(testList));
+    }
+
+    @Test
+    public void testToCollection() {
+        assertTrue(ObjectModel.toCollection(null).isEmpty());
+        assertTrue(ObjectModel.toCollection(new StringBuilder()).isEmpty());
+        Integer[] testArray = new Integer[] {1, 2, 3};
+        List testList = Arrays.asList(testArray);
+        Map<String, Integer> map = new HashMap<String, Integer>() {{
+            put("one", 1);
+            put("two", 2);
+        }};
+        assertEquals(testList, ObjectModel.toCollection(testArray));
+        assertEquals(testList, ObjectModel.toCollection(testList));
+        assertEquals(map.keySet(), ObjectModel.toCollection(map));
+        Vector vector = new Vector(testList);
+        assertEquals(testList, ObjectModel.toCollection(vector.elements()));
+        assertEquals(testList, ObjectModel.toCollection(testList.iterator()));
+        assertEquals(testList, ObjectModel.toCollection(new Bag<>(testArray)));
+        String stringObject = "test";
+        Integer numberObject = 1;
+        Collection stringCollection = ObjectModel.toCollection(stringObject);
+        assertTrue(stringCollection.size() == 1 && stringCollection.contains(stringObject));
+        Collection numberCollection = ObjectModel.toCollection(numberObject);
+        assertTrue(numberCollection.size() == 1 && numberCollection.contains(numberObject));
+    }
+
+    @Test
+    public void testCollectionToString() {
+        Integer[] testArray = new Integer[] {1, 2, 3};
+        List testList = Arrays.asList(testArray);
+        assertEquals("1,2,3", ObjectModel.collectionToString(testList));
+    }
+
+    @Test
+    public void testFromIterator() {
+        Integer[] testArray = new Integer[] {1, 2, 3};
+        List testList = Arrays.asList(testArray);
+        assertEquals(testList, ObjectModel.fromIterator(testList.iterator()));
+    }
+
+    @Test
+    public void testResolveProperty() {
+        assertNull(ObjectModel.resolveProperty(null, null));
+        Integer[] testArray = new Integer[] {1, 2, 3};
+        assertEquals(2, ObjectModel.resolveProperty(testArray, 1));
+        assertNull(ObjectModel.resolveProperty(testArray, 3));
+        assertNull(ObjectModel.resolveProperty(testArray, -1));
+        List<Integer> testList = Arrays.asList(testArray);
+        assertEquals(2, ObjectModel.resolveProperty(testList, 1));
+        assertNull(ObjectModel.resolveProperty(testList, 3));
+        assertNull(ObjectModel.resolveProperty(testList, -1));
+        Map<String, Integer> map = new HashMap<String, Integer>() {{
+            put("one", 1);
+            put("two", 2);
+        }};
+        assertEquals(1, ObjectModel.resolveProperty(map, "one"));
+        assertNull(ObjectModel.resolveProperty(map, null));
+        assertNull(ObjectModel.resolveProperty(map, ""));
+        Person johnDoe = AdultFactory.createAdult("John", "Doe");
+        assertEquals("Expected to be able to access public static final constants.", 1l, ObjectModel.resolveProperty(johnDoe, "CONSTANT"));
+        assertNull("Did not expect to be able to access public fields from package protected classes.", ObjectModel.resolveProperty(johnDoe,
+                "TODAY"));
+        assertEquals("Expected to be able to access an array's length property.", 3, ObjectModel.resolveProperty(testArray, "length"));
+        assertNotNull("Expected not null result for invocation of interface method on implementation class.",
+                ObjectModel.resolveProperty(johnDoe, "lastName"));
+        assertNull("Expected null result for public method available on implementation but not exposed by interface.", ObjectModel
+                .resolveProperty(johnDoe, "fullName"));
+        assertNull("Expected null result for inexistent method.", ObjectModel.resolveProperty(johnDoe, "nomethod"));
+    }
+
+
+    private class Bag<T> implements Iterable<T> {
+
+        private T[] backingArray;
+
+        public Bag(T[] array) {
+            this.backingArray = array;
+        }
+
+        @Override
+        public Iterator<T> iterator() {
+            return new Iterator<T>() {
+
+                int index = 0;
+
+                @Override
+                public boolean hasNext() {
+                    return index < backingArray.length;
+                }
+
+                @Override
+                public T next() {
+                    return backingArray[index++];
+                }
+
+                @Override
+                public void remove() {
+
+                }
+            };
+        }
+    }
+}