/*
* Orika - simpler, better and faster Java bean mapping
*
* Copyright (C) 2011 Orika authors
*
* Licensed 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 ma.glasnost.orika.impl.generator;
import static java.lang.String.format;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import ma.glasnost.orika.Converter;
import ma.glasnost.orika.MapEntry;
import ma.glasnost.orika.impl.util.ClassUtil;
import ma.glasnost.orika.metadata.NestedProperty;
import ma.glasnost.orika.metadata.Property;
import ma.glasnost.orika.metadata.Type;
import java.util.Collection;
/**
* VariableRef represents a reference to a given variable or property; it
* contains various helper methods to properly set it's value and interrogate
* it's underlying property or type. It also returns a properly type-safe cast
* of it as the toString() method, so it can safely be used directly as a
* replacement parameter for source code statements.
*
*
* @author matt.deboer@gmail.com
*
*/
public class VariableRef {
protected String name;
private Property property;
private VariableRef owner;
private Type<?> type;
private boolean declared;
private Converter<?, ?> converter;
public VariableRef(Property property, String name) {
this.name = name;
this.property = property;
this.type = property.getType();
}
public VariableRef(Property property, VariableRef anchor) {
this(property, anchor.toString());
}
public VariableRef(Type<?> type, String name) {
this.name = name;
this.type = type;
}
public void setConverter(Converter<?, ?> converter) {
this.converter = converter;
}
public Converter<?, ?> getConverter() {
return converter;
}
public void setOwner(VariableRef owner) {
this.owner = owner;
}
public VariableRef getOwner() {
return owner;
}
protected String getter() {
return property == null ? name : getGetter(property, name);
}
protected String setter() {
return property == null ? name + " = %s" : getSetter(property, name);
}
public boolean isReadable() {
return getter() != null;
}
public boolean isAssignable() {
return setter() != null;
}
public Class<?> rawType() {
return type.getRawType();
}
/**
* @return the Property (if any) associated with this VariableRef
*/
public Property property() {
return property;
}
public Type<?> type() {
return type;
}
public boolean isPrimitive() {
return type.isPrimitive();
}
public boolean isArray() {
return property != null ? property.isArray() : type.getRawType().isArray();
}
public boolean isCollection() {
return property != null ? property.isCollection() : Collection.class.isAssignableFrom(rawType());
}
public boolean isList() {
return property != null ? property.isList() : List.class.isAssignableFrom(rawType());
}
public boolean isSet() {
return property != null ? property.isSet() : Set.class.isAssignableFrom(rawType());
}
public boolean isMap() {
return property != null ? property.isMap() : Map.class.isAssignableFrom(rawType());
}
public boolean isMapEntry() {
return Entry.class.isAssignableFrom(rawType());
}
public boolean isWrapper() {
return type.isPrimitiveWrapper();
}
public String wrapperTypeName() {
return ClassUtil.getWrapperType(rawType()).getCanonicalName();
}
public VariableRef elementRef(String name) {
return new VariableRef(elementType(), name);
}
public String elementTypeName() {
return elementType() != null ? elementType().getCanonicalName() : null;
}
public Type<?> elementValueType() {
if (type.getRawType().isArray()) {
return type.getComponentType();
} else if (type.isMap()) {
return type.getNestedType(1);
} else {
return type.getNestedType(0);
}
}
@SuppressWarnings("unchecked")
public Type<?> elementType() {
if (type.getRawType().isArray()) {
return type.getComponentType();
} else if (type.isMap()) {
return MapEntry.concreteEntryType((Type<Map<Object, Object>>) type);
} else {
return property != null ? property.getElementType() : ((Type<?>) type.getActualTypeArguments()[0]);
}
}
public Type<?> mapKeyType() {
if (isMap()) {
return type().getNestedType(0);
}
return null;
}
public Type<?> mapValueType() {
if (isMap()) {
return type().getNestedType(1);
}
return null;
}
public String typeName() {
return type.getCanonicalName();
}
public String asWrapper() {
String ref = getter();
if (isPrimitive()) {
ref = ClassUtil.getWrapperType(rawType()).getCanonicalName() + ".valueOf(" + ref + ")";
}
return ref;
}
/**
* Generates code to perform assignment to this VariableRef.
*
* @param value
* @param replacements
* @return
*/
public String assign(String value, Object... replacements) {
if (setter() != null) {
return assignIfPossible(value, replacements);
} else {
throw new IllegalArgumentException("Attempt was made to generate assignment/setter code for [" + name + "."
+ (property != null ? property : type) + "] which has no setter/assignment method");
}
}
/**
* Generates code to perform assignment to this VariableRef, if it is
* assignable.
*
* @param value
* @param replacements
* @return
*/
public String assignIfPossible(String value, Object... replacements) {
if (setter() != null) {
String expr = format(value, replacements);
expr = cast(expr);
return format(setter(), expr);
} else {
return "";
}
}
/**
* Generates code to perform assignment to this VariableRef, if it is
* assignable.
*
* @param value
* @param replacements
* @return
*/
public String assignIfPossible(VariableRef value) {
if (setter() != null) {
return format(setter(), cast(value));
} else {
return "";
}
}
/**
* Returns java code which assigns the value of the provided PropertyRef to
* this PropertyRef
*
* @param value
* @return
*/
public String assign(VariableRef value) {
if (setter() != null) {
String expr = value.toString();
if (value.type().isPrimitive() && type.isPrimitiveWrapper()) {
String wrapperClass = ClassUtil.getWrapperType(rawType()).getCanonicalName();
expr = format("(%s) %s.valueOf(%s)", wrapperClass, wrapperClass, expr);
} else if (type.isPrimitive() && value.type().isPrimitiveWrapper()) {
expr = format("((%s)%s).%sValue()", ClassUtil.getWrapperType(rawType()).getCanonicalName(), expr, primitiveType(rawType()));
} else if (type.isArray() && value.isCollection()) {
if (type.getComponentType().isPrimitive() && value.elementType().isPrimitiveWrapper()) {
String wrapperType = ClassUtil.getWrapperType(rawType().getComponentType()).getCanonicalName();
expr = format("(%s[])%s.toArray(new %s[0])", wrapperType, expr, wrapperType);
} else {
expr = format("(%s[])%s.toArray(new %s[0])", value.elementType().getCanonicalName(), expr, value.elementType()
.getCanonicalName());
}
} else if (!type.isArray() && value.type().isPrimitive() && !type.isPrimitive()) {
expr = format("%s.valueOf(%s)", value.type.getWrapperType().getCanonicalName(), expr);
}
return format(setter(), expr);
} else {
return "";
}
}
public String cast(VariableRef ref) {
return cast(ref, type());
}
/**
* Returns Java code which provides a cast of the specified value to the
* type of this property ref
*
* @param value
* @return
*/
public String cast(String value) {
return cast(value, type());
}
/**
* Returns Java code which provides a cast of the specified value to the
* type of this property ref
*
* @param value
* @return
*/
protected static String cast(String value, Type<?> type) {
String castValue = value.trim();
String typeName = type.getCanonicalName();
/*
* Avoid double-cast
*/
if (!"null".equals(value)) {
if (type.isPrimitive()) {
if (type.getRawType() == Character.TYPE) {
castValue = format("(\"\"+%s).charAt(0)", castValue);
} else if (!isPrimitiveLiteral(castValue, type)) {
castValue = format("%s.valueOf(\"\"+%s).%sValue()", type.getWrapperType().getCanonicalName(), castValue, type);
}
} else if (type.isPrimitiveWrapper() && isPrimitiveLiteral(castValue, type)) {
castValue = format("%s.valueOf(%s)", type.getWrapperType().getCanonicalName(), castValue);
} else if (type.isString()) {
castValue = "\"\" + " + castValue;
} else if (!value.startsWith("(" + typeName + ")")) {
castValue = "((" + typeName + ")" + castValue + ")";
}
}
return castValue;
}
/**
* Returns Java code which provides a cast of the specified value to the
* type of this property ref
*
* @param value
* @return
*/
protected static String cast(VariableRef value, Type<?> type) {
String castValue = value.toString();
String typeName = type.getCanonicalName();
if (type.isPrimitive()) {
if (value.isWrapper()) {
castValue = format("%s.%sValue()", castValue, type);
} else if (Character.TYPE == type.getRawType() && value.type().isString()) {
castValue = format("%s.charAt(0)", value);
} else if (!value.isPrimitive()) {
castValue = format("%s.valueOf(\"\"+%s).%sValue()", type.getWrapperType().getCanonicalName(), castValue, typeName);
}
} else if (type.isPrimitiveWrapper()) {
if (value.isPrimitive()) {
castValue = format("%s.valueOf(%s)", type.getCanonicalName(), castValue);
}
} else if (type.isString()) {
castValue = "\"\" + " + castValue;
} else {
castValue = "((" + typeName + ")" + castValue + ")";
}
return castValue;
}
/**
* Returns Java code which declares this variable, initialized with it's
* default value.
*
* @return the code which declares this variable, and explicitly assigns
* it's default value.
*/
public String declare() {
declared = true;
return format("\n%s %s = %s", typeName(), name(), getDefaultValue(rawType()));
}
/**
* Returns Java code which declares this variable, initialized with the
* provided value.
*
* @param value
* the value to assign
* @param args
* any replacement arguments to applied to value via
* String.format()
* @return the code which declares this variable, and explicitly assigns the
* provided value.
*/
public String declare(String value, Object... args) {
String valueExpr = format(value, args);
valueExpr = cast(valueExpr);
declared = true;
return format("\n%s %s = %s", typeName(), name(), valueExpr);
}
/**
* Returns Java code which declares this variable, initialized with the
* provided value.
*
* @param value
* the value to assign
* @param args
* any replacement arguments to applied to value via
* String.format()
* @return the code which declares this variable, and explicitly assigns the
* provided value.
*/
public String declare(VariableRef ref) {
return declare(cast(ref));
}
public boolean isDeclared() {
return declared;
}
/**
* Returns the Java code which represents the default value for the
* specified type
*
* @param clazz
* @return
*/
public static String getDefaultValue(Class<?> clazz) {
if (Boolean.TYPE.equals(clazz))
return "false";
else if (Character.TYPE.equals(clazz))
return "'\\u0000'";
else if (clazz.isPrimitive())
return "0";
else
return "null";
}
public String primitiveType() {
return primitiveType(rawType());
}
public String primitiveType(Class<?> clazz) {
String type = clazz.getSimpleName().toLowerCase();
if ("integer".equals(type)) {
type = "int";
} else if ("character".equals(type)) {
type = "char";
}
return type;
}
public String owner() {
return name;
}
public String name() {
return property != null ? property.getName() : name;
}
public String isNull() {
return property != null ? isNull(property, name) : getter() + " == null";
}
/**
* Removes the outermost property from a nested getter expression
*
* @param expression
* @return
*/
private static String unwrap(String expression) {
// TODO: should write a getParentGetter() instead of unwrapping
if (expression.startsWith("((")) {
expression = expression.substring(expression.indexOf(")") + 1, expression.length() - 1);
if (expression.endsWith("]")) {
expression = expression.substring(0, expression.lastIndexOf("["));
} else {
expression = expression.substring(0, expression.lastIndexOf("."));
}
}
return expression;
}
private static String isNull(Property property, String name) {
if (property == null) {
return name + " == null";
} else {
String getterNull = getGetter(property, name) + " == null";
if (property.isListElement()) {
return "(" + unwrap(getGetter(property, name)) + ".size() <= " + property.getName().replaceAll("[\\[\\]]", "") + " || "
+ getterNull + ")";
} else if (property.isArrayElement()) {
return "(" + unwrap(getGetter(property, name)) + ".length <= " + property.getName().replaceAll("[\\[\\]]", "") + " || "
+ getterNull + ")";
} else {
return getterNull;
}
}
}
/**
* @return a nested-property safe null check for this property
*/
public String notNull() {
StringBuilder path = new StringBuilder();
path.append("(");
if (property() != null && property().hasPath()) {
boolean first = true;
String expression = this.name;
for (final Property p : property().getPath()) {
if (!first) {
path.append(" && ");
} else {
first = false;
}
path.append(format("!(%s)", isNull(p, expression)));
expression = getGetter(p, expression);
}
}
if (path.length() > 1) {
path.append(" && ");
}
path.append(format("!(%s)", isNull(property, name)));
path.append(")");
return path.toString();
}
public String ifNotNull() {
return "if ( " + notNull() + ") ";
}
public String ifNull() {
return "if ( " + isNull() + ") ";
}
public String toString() {
return getter();
}
/**
* @return whether or not this VariableRef represents a nested property
*/
public boolean isNestedProperty() {
return property != null && property.hasPath();
}
public List<VariableRef> getPath() {
if (property != null && property.hasPath()) {
Property[] propPath = property.getPath();
List<VariableRef> path;
if (propPath.length > 1) {
path = new ArrayList<VariableRef>(propPath.length);
path.add(new VariableRef(propPath[0], name));
String[] expr = property.getExpression().split("\\.");
for (int i = 1; i < propPath.length; ++i) {
Property[] nestedPath = new Property[i];
System.arraycopy(propPath, 0, nestedPath, 0, i);
path.add(new VariableRef(new NestedProperty(join(expr, ".", 0, i + 1), propPath[i], nestedPath), name));
}
} else {
path = Collections.singletonList(new VariableRef(propPath[0], name));
}
return path;
} else {
return Collections.emptyList();
}
}
private static String join(Object[] array, String separator, int startIndex, int endIndex) {
if (array == null) {
return null;
}
int bufSize = (endIndex - startIndex);
if (bufSize <= 0) {
return "";
}
bufSize *= ((array[startIndex] == null ? 16 : array[startIndex].toString().length()) + 1);
StringBuilder buf = new StringBuilder(bufSize);
for (int i = startIndex; i < endIndex; i++) {
if (i > startIndex) {
buf.append(separator);
}
if (array[i] != null) {
buf.append(array[i]);
}
}
return buf.toString();
}
public String assertType() {
return "if(!(" + name + " instanceof " + typeName() + ")) throw new IllegalStateException(\"[" + name + "] is not an instance of "
+ typeName() + " \");";
}
/**
* Generates java code for a reference to the "size" of this VariableRef
*
* @return
*/
public String size() {
return getter() + "." + (rawType().isArray() ? "length" : "size()");
}
protected static String getGetter(final Property property, String variableExpression) {
if (property.getGetter() == null)
return null;
String var = variableExpression;
if (property.hasPath()) {
for (final Property p : property.getPath()) {
var = getGetter(p, var);
}
}
String getter = "((" + property.getType().getCanonicalName() + ")" + var;
if (!property.isArrayElement() && !"".equals(property.getName()) && !property.getGetter().startsWith("[")) {
getter += "." + property.getGetter() + ")";
} else {
getter += property.getGetter() + ")";
}
return getter;
}
public String isInstanceOf(Type<?> type) {
return format("(%s instanceof %s)", getter(), type.getCanonicalName());
}
/**
* Returns a fully type-cast setter for the property which has no reliance
* on java generics.
*
* @param property
* the Property for which to return the getter
* @param variableExpression
* the String value to use for the variable on which the getter
* is called
* @return
*/
protected static String getSetter(final Property property, final String variableExpression) {
if (property.getSetter() == null)
return null;
String var = variableExpression;
if (property.hasPath()) {
for (final Property p : property.getPath()) {
var = getGetter(p, var);
}
}
return var + (property.isArrayElement() || "".equals(property.getName()) || property.getSetter().startsWith("[") ? "" : ".")
+ property.getSetter();
}
/**
* Return Java code which avoids a NullPointerException when accessing this
* variable reference; if it is not backed by a nested property, this method
* returns the empty string.
*
* @return
*/
public String ifPathNotNull() {
StringBuilder path = new StringBuilder();
if (property.hasPath()) {
boolean first = true;
path.append("if(");
String expression = this.name;
for (final Property p : property.getPath()) {
if (!first) {
path.append(" && ");
} else {
first = false;
}
path.append(format("!(%s)", isNull(p, expression)));
expression = getGetter(p, expression);
}
path.append(")");
}
return path.toString();
}
protected static boolean isPrimitiveLiteral(String expr, Type<?> type) {
if (type.isPrimitive()) {
String primitiveType = type.getCanonicalName();
if ("boolean".equals(primitiveType)) {
return expr.matches("(true|false)");
} else if ("char".equals(primitiveType)) {
return expr.matches("^'\\w+'$");
} else if ("int".equals(primitiveType) || "short".equals(primitiveType)) {
return expr.matches("\\d+");
} else if ("long".equals(primitiveType)) {
return expr.matches("\\d+(l|L)?");
} else if ("float".equals(primitiveType)) {
return expr.matches("\\d*(\\.\\d*)?(f|F)?");
} else if ("double".equals(primitiveType)) {
return expr.matches("\\d+(\\.\\d*)?");
}
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((property == null) ? 0 : property.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
VariableRef other = (VariableRef) obj;
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
if (property == null) {
if (other.property != null) {
return false;
}
} else if (!property.equals(other.property)) {
return false;
}
if (type == null) {
if (other.type != null) {
return false;
}
} else if (!type.equals(other.type)) {
return false;
}
return true;
}
}