org.apache.sling.scripting.sightly.compiler.util.ObjectModel Maven / Gradle / Ivy
/*******************************************************************************
* 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);
private static final String EMPTY_STRING = "";
/**
* A {@link Set} that stores all the supported primitive classes.
*/
public static final Set> PRIMITIVE_CLASSES;
static {
Set> 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}.
*
*
* The property can be either an index or a name:
*
*
* - 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
*
* - 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)})
*
*
* @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(property);
}
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:
*
*
* - if the {@code object} is {@code null} the returned value is {@code false}
* - if the {@code object} is a {@link Number} the method will return {@code false} only if the number's value is 0
* - if the {@link String} representation of the {@code object} is equal irrespective of its casing to "true", the method will
* return {@code true}
* - 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
* - if the object is an array, the method will return {@code true} only if the array is not empty
*
*
* @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 (EMPTY_STRING.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:
*
*
* - if the {@code object} is {@code null} an empty string will be returned
* - if the {@code object} is an instance of a {@link String} the object itself will be returned
* - if the object is a primitive (see {@link #isPrimitive(Object)}), its {@link String} representation will be returned
* - if the object is an {@link Enum} its name will be returned (see {@link Enum#name()})
* - 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
*
*
* @param object the target object
* @return the string representation of the object or an empty string
*/
public static String toString(Object object) {
String output = EMPTY_STRING;
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:
*
*
* - if the {@code object} is {@code null} an empty collection will be returned
* - if the {@code object} is an array a list transformation of the array will be returned
* - if the {@code object} is a {@link Collection} the object itself will be returned
* - if the {@code object} is an instance of a {@link Map} the map's key set will be returned (see {@link Map#keySet()})
* - if the {@code object} is an instance of an {@link Enumeration} a list transformation will be returned
* - if the {@code object} is an instance of an {@link Iterator} or {@link Iterable} the result of {@link #fromIterator(Iterator)}
* will be returned
* - if the {@code object} is an instance of a {@link String} or {@link Number} a {@link Collection} containing only this
* object will be returned
* - any other case not covered by the previous rules will result in an empty {@link Collection}
*
*
* @param object the target object
* @return the collection representation of the object
*/
public static Collection