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() {
+
+ }
+ };
+ }
+ }
+}