/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.core.persistence; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import org.apache.commons.lang.StringUtils; import org.eclipse.skalli.model.EntityBase; import org.eclipse.skalli.model.ExtensibleEntityBase; import org.eclipse.skalli.model.ExtensionEntityBase; public final class EntityHelper { /** * Normalizes an entity and its extensions: * <ol> * <li>Replaces a <code>null</code> {@link java.lang.String} field with a blank ("") string.</li> * <li>Replaces a <code>null</code> {@link java.util.Collection}-like field with an empty collection * of matching type (i.e. an instance of {@link java.util.ArrayList} is assigned to a field of type * <code>ArrayList</code>, an instance of {@link java.util.HashSet} is assigned to a field of type * <code>HashSet</code> and so on). An <code>ArrayList</code> is assigned to a field of type * {@link java.util.List}, a <code>HashSet</code> is assigned to a field of type {@link java.util.Set} and * a {@link java.util.TreeSet} is assigned to a field of type {@link java.util.SortedSet}.</li> * <li>Replaces a <code>null</code> {@link java.util.Map}-like field with an empty map * of matching type (i.e. an instance of {@link java.util.HashMap} is assigned to a field of type * <code>HashMap</code>, and so on). A <code>HashMap</code> is assigned to a field type <code>Map</code>, * while a {@link java.util.TreeMap</code> is assigned to a field of type {@link java.utilSortedMap}.</li> * <li>Removes blank strings from {@link java.util.Collection}-like fields and from the values of * {@link java.util.Map}-like fields. Entries that are not strings are ignored.</li> * </ol> * All other kinds of fields are ignored.<br> * This method assumes, that the collection type to be * instantiated has either a constructor with a single integer argument (specifying the initial capacity * of the collection), or a parameterless constructor. The former is preferred.<br> * This method iterates the entity and, in case the entity is an instance * of {@link ExtensibleEntityBase}, all extensions of that entity, too. */ public static void normalize(EntityBase entity) { doNormalize(entity); if (entity instanceof ExtensibleEntityBase) { ExtensibleEntityBase extensibleEntity = (ExtensibleEntityBase) entity; for (ExtensionEntityBase extension : extensibleEntity.getAllExtensions()) { extension.setExtensibleEntity(extensibleEntity); doNormalize(extension); } } } private static void doNormalize(EntityBase entity) { if (entity == null) { return; } Class<?> currentClass = entity.getClass(); while (currentClass != null) { for (Field field : currentClass.getDeclaredFields()) { try { // do not try to change constants or transient fields int modifiers = field.getModifiers(); if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) { continue; } field.setAccessible(true); // ensure, thet the value is non null Object value = field.get(entity); if (value == null) { instantiateField(entity, field); } else { // for non-null collections or maps, ensure that there // are no null entries or empty strings Class<?> type = field.getType(); if (Collection.class.isAssignableFrom(type)) { Collection<?> collection = (Collection<?>) value; ArrayList<Object> remove = new ArrayList<Object>(); for (Object entry : collection) { if (entry instanceof String && StringUtils.isBlank((String) entry)) { remove.add(entry); } } collection.removeAll(remove); field.set(entity, collection); } else if (Map.class.isAssignableFrom(type)) { Map<?, ?> map = (Map<?, ?>) value; ArrayList<Object> remove = new ArrayList<Object>(); for (Entry<?, ?> entry : map.entrySet()) { if (entry.getValue() instanceof String && StringUtils.isBlank((String) entry.getValue())) { remove.add(entry.getKey()); } } for (Object key : remove) { map.remove(key); } field.set(entity, map); } } } catch (UnsupportedOperationException e) { // TODO exception handling/logging // some collections/map may not support remove throw new RuntimeException(e); } catch (IllegalArgumentException e) { //TODO exception handling/logging throw new RuntimeException(e); } catch (IllegalAccessException e) { //TODO exception handling/logging throw new RuntimeException(e); } } currentClass = currentClass.getSuperclass(); } } private static void instantiateField(EntityBase entity, Field field) throws IllegalArgumentException, IllegalAccessException { Class<?> type = field.getType(); if (type.equals(String.class)) { field.set(entity, ""); //$NON-NLS-1$ } else if (type.equals(List.class)) { field.set(entity, new ArrayList<Object>(0)); } else if (type.equals(Set.class)) { field.set(entity, new HashSet<Object>(0)); } else if (type.equals(SortedSet.class)) { field.set(entity, new TreeSet<Object>()); } else if (type.equals(Map.class)) { field.set(entity, new HashMap<Object, Object>(0)); } else if (type.equals(SortedMap.class)) { field.set(entity, new TreeMap<Object, Object>()); } else if (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)) { field.set(entity, getInstance(type)); } } /** * Returns an instance of the given collection type. * This method assumes, that the type has either a constructor * with a single integer argument (specifying the initial capacity * of the collection), or a parameterless constructor. The former * is preferred. * @param collectionType the collection type to instantiate. */ private static Object getInstance(Class<?> collectionType) { Object instance = null; try { try { Constructor<?> constructor = collectionType.getConstructor(Integer.class); instance = constructor.newInstance(0); } catch (NoSuchMethodException e) { instance = collectionType.newInstance(); } } catch (Exception e) { //TODO exception handling/logging throw new RuntimeException(e); } return instance; } }