| /******************************************************************************* |
| * 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; |
| } |
| |
| |
| } |