/** * AnalyzerBeans * Copyright (C) 2014 Neopost - Customer Information Management * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.eobjects.analyzer.util.convert; import java.io.ByteArrayInputStream; import java.io.File; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.inject.Inject; import org.apache.commons.lang.SerializationUtils; import org.eobjects.analyzer.beans.api.Alias; import org.eobjects.analyzer.beans.api.Converter; import org.eobjects.analyzer.beans.convert.ConvertToDateTransformer; import org.eobjects.analyzer.beans.convert.ConvertToNumberTransformer; import org.eobjects.analyzer.util.ChangeAwareObjectInputStream; import org.eobjects.analyzer.util.ReflectionUtils; import org.apache.metamodel.util.FileHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Default implementation of the {@link Converter} interface. This converter is * able to convert single instances (not arrays or collections) of: * * <ul> * <li>Boolean</li> * <li>Byte</li> * <li>Short</li> * <li>Integer</li> * <li>Long</li> * <li>Float</li> * <li>Double</li> * <li>Character</li> * <li>String</li> * <li>enums</li> * <li>java.io.File</li> * <li>java.util.Date</li> * <li>java.sql.Date</li> * <li>java.util.Calendar</li> * <li>java.util.regex.Pattern</li> * </ul> * * If given a {@link Serializable} type it will also attempt serializing it to a * byte-array string. */ public class StandardTypeConverter implements Converter<Object> { private static final Logger logger = LoggerFactory.getLogger(StandardTypeConverter.class); // ISO 8601 private static final String dateFormatString = "yyyy-MM-dd'T'HH:mm:ss S"; @Inject Converter<Object> parentConverter; public StandardTypeConverter() { this(null); } public StandardTypeConverter(Converter<Object> parentConverter) { this.parentConverter = parentConverter; } @Override public Object fromString(Class<?> type, String str) { if (ReflectionUtils.isString(type)) { return str; } if (ReflectionUtils.isBoolean(type)) { return Boolean.valueOf(str); } if (ReflectionUtils.isCharacter(type)) { return Character.valueOf(str.charAt(0)); } if (ReflectionUtils.isInteger(type)) { return Integer.valueOf(str); } if (ReflectionUtils.isLong(type)) { return Long.valueOf(str); } if (ReflectionUtils.isByte(type)) { return Byte.valueOf(str); } if (ReflectionUtils.isShort(type)) { return Short.valueOf(str); } if (ReflectionUtils.isDouble(type)) { return Double.valueOf(str); } if (ReflectionUtils.isFloat(type)) { return Float.valueOf(str); } if (ReflectionUtils.is(type, Class.class)) { try { return Class.forName(str); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Class not found: " + str, e); } } if (type.isEnum()) { try { Object[] enumConstants = type.getEnumConstants(); // first look for enum constant matches Method nameMethod = Enum.class.getMethod("name"); for (Object e : enumConstants) { String name = (String) nameMethod.invoke(e); if (name.equals(str)) { return e; } } // check for aliased enums for (Object e : enumConstants) { String name = (String) nameMethod.invoke(e); Field field = type.getField(name); Alias alias = ReflectionUtils.getAnnotation(field, Alias.class); if (alias != null) { String[] aliasValues = alias.value(); for (String aliasValue : aliasValues) { if (aliasValue.equals(str)) { return e; } } } } } catch (Exception e) { throw new IllegalStateException("Unexpected error occurred while examining enum", e); } throw new IllegalArgumentException("No such enum '" + str + "' in enum class: " + type.getName()); } if (ReflectionUtils.isDate(type)) { return toDate(str); } if (ReflectionUtils.is(type, File.class)) { return new File(str.replace('\\', File.separatorChar)); } if (ReflectionUtils.is(type, Calendar.class)) { Date date = toDate(str); Calendar c = Calendar.getInstance(); c.setTime(date); return c; } if (ReflectionUtils.is(type, Pattern.class)) { try { return Pattern.compile(str); } catch (PatternSyntaxException e) { throw new IllegalArgumentException("Invalid regular expression syntax in '" + str + "'.", e); } } if (ReflectionUtils.is(type, java.sql.Date.class)) { Date date = toDate(str); return new java.sql.Date(date.getTime()); } if (ReflectionUtils.isNumber(type)) { return ConvertToNumberTransformer.transformValue(str); } if (ReflectionUtils.is(type, Serializable.class)) { logger.warn("fromString(...): No built-in handling of type: {}, using deserialization", type.getName()); byte[] bytes = (byte[]) parentConverter.fromString(byte[].class, str); ChangeAwareObjectInputStream objectInputStream = null; try { objectInputStream = new ChangeAwareObjectInputStream(new ByteArrayInputStream(bytes)); objectInputStream.addClassLoader(type.getClassLoader()); Object obj = objectInputStream.readObject(); return obj; } catch (Exception e) { throw new IllegalStateException("Could not deserialize to " + type + ".", e); } finally { FileHelper.safeClose(objectInputStream); } } throw new IllegalArgumentException("Could not convert to type: " + type.getName()); } @Override public String toString(Object o) { if (o instanceof Calendar) { // will now be picked up by the date conversion o = ((Calendar) o).getTime(); } final String result; if (o instanceof Boolean || o instanceof Number || o instanceof String || o instanceof Character) { result = o.toString(); } else if (o instanceof File) { File file = (File) o; if (file.isAbsolute()) { result = file.getAbsolutePath().replace('\\','/'); } else { result = file.getPath().replace('\\','/'); } } else if (o instanceof Date) { if (o instanceof ExpressionDate) { // preserve the expression if it is an ExpressionDate result = ((ExpressionDate) o).getExpression(); } else { result = new SimpleDateFormat(dateFormatString).format((Date) o); } } else if (o instanceof Pattern) { result = o.toString(); } else if (o instanceof Enum) { return ((Enum<?>) o).name(); } else if (o instanceof Class) { result = ((Class<?>) o).getName(); } else if (o instanceof Serializable) { logger.info("toString(...): No built-in handling of type: {}, using serialization.", o.getClass().getName()); byte[] bytes = SerializationUtils.serialize((Serializable) o); result = parentConverter.toString(bytes); } else { logger.warn("toString(...): Could not convert type: {}", o.getClass().getName()); result = o.toString(); } return result; } private static final Date toDate(String str) { try { return new SimpleDateFormat(dateFormatString).parse(str); } catch (ParseException e) { Date date = ConvertToDateTransformer.getInternalInstance().transformValue(str); if (date == null) { logger.error("Could not parse date: " + str, e); throw new IllegalArgumentException(e); } else { return date; } } } @Override public boolean isConvertable(Class<?> type) { return ReflectionUtils.is(type, Serializable.class) || type.isPrimitive(); } }