/** * Copyright 2008-2016 Qualogy Solutions B.V. * * 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 com.qualogy.qafe.business.integration.adapter; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import com.qualogy.qafe.business.integration.builder.PredefinedClassTypeConverter; import com.qualogy.qafe.core.application.MappingError; import com.qualogy.qafe.core.datastore.DataMap; /** * Utility class to convert a object into a Map. * @author mvanderwurff * */ public class ObjectMapConverter { /** * the default class up to where fields can be read from, convert will not include * the up to class. */ public final static Class OBJECT_CLASS = Object.class; /** * the defaults for excluding packages to read fields from when doing an * intrusive field conversion (including a null package) */ public final static String[] DEFAULT_EXCLUDE_PCK_NAMES = {"java", "javax"}; public static final String KEYWORD_FQN = "fqn"; /** * Method to convert a given object into a map. The given object * cannot be of type Collection or an Array of objects. * * 1) If the given object is a Map or is not null and not predefined * the object will be converted to Map and the objects within will * be converted to maps as well (when not predefinedtypes and not null, nut including * Collections and Arrays). * 2) In any other case the object is returned * * @param object - the object to convert * @throws IllegalArgumentException - when the object arg is of type Collection or an * array of objects * @return map - the object converted to a Map */ public static Object convert(Object object){ Object converted = object; if(object instanceof Map){ converted = convertMap((Map)object, new HashSet()); }else if(object instanceof Collection || object instanceof Object[]){ converted = convertObject(object,new HashSet()); }else if(object != null && !PredefinedClassTypeConverter.isPredefined(object.getClass())){ converted = (Map)convertObject(object, new HashSet()); } return converted; } public static Object convert(String className, Map map){ Object result = null; try { result = convert(Class.forName(className), map); } catch (ClassNotFoundException e) { throw new UnableToAdaptException(e); } return result; } private static Object convert(Class clazz, Map map){ Object result = createServiceObj(clazz); for (Iterator iter = map.keySet().iterator(); iter.hasNext();) { String key = (String) iter.next(); Object param = map.get(key); Field field; try { field = result.getClass().getDeclaredField(key); if(param instanceof Map){ param = convert(field.getClass(), (Map)param); }else if(param instanceof List || param instanceof Object[]){ if(param instanceof Object[])param = Arrays.asList((Object[])param); for (int i = 0; i<((List)param).size(); i++) { if(param!=null && !PredefinedClassTypeConverter.isPredefined(param.getClass())) throw new UnableToAdaptException("cannot adapt nested complex objects to an instance without adapter"); } } field.setAccessible(true); field.set(result, param); } catch (NoSuchFieldException e) { Logger.getLogger(ObjectMapConverter.class.getName()).log(Level.WARNING, "field " + key + " does not exist on object " + clazz, e);//throw new MappingError("field " + key + " does not exist on object " + clazz, e); } catch (Exception e) { throw new UnableToAdaptException("field [" + key + "] param [" + param + "]", e); } } return result; } private static Object createServiceObj(Class clazz){ Object result = null; try { if(clazz==null) throw new UnableToAdaptException("Adapter states null outputclass"); Constructor c = clazz.getConstructor(new Class[0]); c.setAccessible(true); result = c.newInstance(new Object[0]); } catch (InstantiationException e) { throw new MappingError("Cannot instantiate ["+clazz+"], hint: define a default constructor", e); } catch (Exception e) { throw new UnableToAdaptException(e); } return result; } /** * Method to convert an object to a Map. * @param object * @param entries * @return */ private static Object convertObject(Object object, Set entries){ Object converted = null; if (object instanceof Object[]) { object = Arrays.asList((Object[])object); } if(object instanceof Map){ converted = convertMap((Map)object, entries); } else if (object instanceof Collection){ converted = object; List convertedItems = new ArrayList(); for (Iterator iter = ((Collection)converted).iterator(); iter.hasNext();) { Object convertedItem = convertObject((Object) iter.next(), entries); if (convertedItem != null) { convertedItems.add(convertedItem); } } converted = convertedItems; } else if (object != null){ converted = getFieldsFromObject(new DataMap(), entries, object.getClass(), object); } return converted; } /** * Method gets all the fields from an object including the private and * protected ones and returns them as a Map, where the key will be the field name * and the value the value of the field. If a field is a complex object (not predefined) * as well, it will be converted as well. * * Note: Ignores fields from java.lang */ private static Map<String, Object> getFieldsFromObject(Map<String, Object> converted, Set<Object> entries, Class clazz, Object object){ if(OBJECT_CLASS.equals(object.getClass())) return converted; if(clazz.getSuperclass() != null && clazz.getSuperclass() != OBJECT_CLASS) { converted = getFieldsFromObject(converted, entries, clazz.getSuperclass(), object); } Field[] fields = clazz.getDeclaredFields(); AccessibleObject.setAccessible(fields, true); for (int i = 0; i < fields.length; i++) { Object value = null; try { //Warning: Field.get(Object) creates wrappers objects for primitive types. value = fields[i].get(object); } catch (IllegalArgumentException e) { //leave } catch (IllegalAccessException e) { //this can't happen. Would get a Security exception instead throw a runtime exception in case the impossible happens. throw new InternalError("Unexpected IllegalAccessException: " + e.getMessage()); } String uniqueEntryIdentifier = fields[i].getDeclaringClass().getName() + ":" + fields[i].getName(); if(entries.add(uniqueEntryIdentifier)){// ???? not already contains, to prevent from statics to be included more than once, raising a stackoverflow // QAFEPLATFORM-88 - java.util collections were also excluded previously.- now added check for Collection if ((value instanceof Collection) || ((value != null) && !PredefinedClassTypeConverter.isPredefined(value.getClass()) && !hasExcludedPackage(value.getClass()))) { value = convertObject(value, entries); } else if (value instanceof Map) { value = convertMap((Map)value, entries); } } converted.put(fields[i].getName(), value); } converted.put(KEYWORD_FQN, clazz.getName()); return converted; } private static boolean hasExcludedPackage(Class clazz){ boolean isExcluded = clazz.getPackage()==null; for (int i = 0; !isExcluded && i < DEFAULT_EXCLUDE_PCK_NAMES.length; i++) { isExcluded = clazz.getPackage().getName().startsWith(DEFAULT_EXCLUDE_PCK_NAMES[i]); } return isExcluded; } /** * Method to convert nested objects within a Map. * TODO: necessary? * @param object * @return */ private static Map<String, Object> convertMap(Map object, Set entries){ Set keys = object.keySet(); for (Iterator iter = keys.iterator(); iter.hasNext();) { Object tempKey = iter.next(); if (tempKey != null){ String key = tempKey.toString(); Object value = object.get(key); if(value != null && !PredefinedClassTypeConverter.isPredefined(value.getClass()) && !hasExcludedPackage(value.getClass())){ value = convertObject(value, entries); } object.put(key, value); } } return object; } }