From a1caaa38d7042c8cfedea8ea4af33ff979f777c5 Mon Sep 17 00:00:00 2001
From: tpylak <tpylak>
Date: Wed, 6 Oct 2010 09:15:22 +0000
Subject: [PATCH] LMS-1767 escaping traverser infrastructure (part 1)

SVN: 18169
---
 .../ReflectionPrimitiveFieldTraverser.java    | 294 ++++++++++++++++++
 1 file changed, 294 insertions(+)
 create mode 100644 common/source/java/ch/systemsx/cisd/common/utilities/ReflectionPrimitiveFieldTraverser.java

diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/ReflectionPrimitiveFieldTraverser.java b/common/source/java/ch/systemsx/cisd/common/utilities/ReflectionPrimitiveFieldTraverser.java
new file mode 100644
index 00000000000..6721b431754
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/utilities/ReflectionPrimitiveFieldTraverser.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2010 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.common.utilities;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Allows to change all fields of the selected primitive type of a specified object. Traverses the
+ * object recursively.
+ * 
+ * @author Tomasz Pylak
+ */
+public class ReflectionPrimitiveFieldTraverser
+{
+    public static interface ReflectionFieldVisitor
+    {
+        /** @return new value for the field, null if field value should not be changed */
+        <T> T tryVisit(T field);
+
+        /** @return true if visiting objects of the specified classes can change visitor state */
+        boolean isVisiting(Class<?> clazz);
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.FIELD)
+    @Inherited
+    public @interface TraversableStructure
+    {
+    }
+
+    /** cannot be called for primitive types or collections */
+    public static void traverse(Object object, ReflectionFieldVisitor fieldVisitor)
+    {
+        Class<?> clazz = object.getClass();
+        new ReflectionPrimitiveFieldTraverser(fieldVisitor).traverseMutable(object, clazz);
+    }
+
+    // mutable classes are arrays and classes which are not collections and not primitives
+    private void traverseMutable(Object object, Class<?> clazz)
+    {
+        if (isCollection(object))
+        {
+            throw new IllegalStateException("Cannot traverse collection " + object);
+        }
+        if (clazz.isPrimitive())
+        {
+            throw new IllegalStateException("Cannot traverse objects of primitive type " + object);
+        }
+
+        if (clazz.isArray())
+        {
+            traverseArray(clazz);
+        } else
+        {
+            traverseFields(object, clazz);
+        }
+    }
+
+    private final ReflectionFieldVisitor visitor;
+
+    private ReflectionPrimitiveFieldTraverser(ReflectionFieldVisitor fieldVisitor)
+    {
+        this.visitor = fieldVisitor;
+    }
+
+    /**
+     * Traverses all non-final and non-static fields with any visibility (including private fields)
+     * of the object recurisively. Changes value of each primitive field if field visitor provides a
+     * new one.
+     */
+    private void traverseFields(Object object, Class<?> clazz)
+    {
+        if (object == null)
+        {
+            return;
+        }
+
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields)
+        {
+            // field.getAnnotations()[0].
+            int modifiers = field.getModifiers();
+            if (Modifier.isFinal(modifiers) == false && Modifier.isStatic(modifiers) == false)
+            {
+                try
+                {
+                    traverseField(object, field);
+                } catch (Exception ex)
+                {
+                    ex.printStackTrace();
+                    throw new IllegalStateException("Sould not happen: " + ex.getMessage());
+                }
+            }
+        }
+    }
+
+    private void traverseField(Object object, Field field) throws IllegalAccessException
+    {
+        field.setAccessible(true);
+        Object fieldValue = field.get(object);
+        if (fieldValue == null)
+        {
+            return;
+        }
+        Class<?> clazz = fieldValue.getClass();
+
+        if (clazz.isArray())
+        {
+            traverseArray(fieldValue);
+        } else if (isCollection(fieldValue))
+        {
+            traverseCollectionField(object, field, (Collection<?>) fieldValue);
+        } else if (clazz.isPrimitive())
+        {
+            Object newValue = visitor.tryVisit(fieldValue);
+            if (newValue != null)
+            {
+                field.set(object, newValue);
+            }
+        } else
+        {
+            traverseFields(fieldValue, clazz);
+        }
+        // TODO 2010-10-05, Tomasz Pylak: handle Map, skip external classes
+    }
+
+    // FIXME 2010-10-05, Tomasz Pylak: handle mutable collections like in traverseArray
+    private void traverseCollectionField(Object object, Field field, Collection<?> collection)
+            throws IllegalArgumentException, IllegalAccessException
+    {
+        if (collection.size() == 0)
+        {
+            return;
+        }
+        Class<?> componentType = figureElementClass(collection);
+        if (componentType.isPrimitive())
+        {
+            if (visitor.isVisiting(componentType))
+            {
+                traversePrimitiveCollection(object, field, collection);
+            }
+        } else
+        {
+            for (Object element : collection)
+            {
+                traverseMutable(element, componentType);
+            }
+        }
+    }
+
+    // assumes that all elements are of the same type
+    private static Class<?> figureElementClass(Collection<?> collection)
+    {
+        Object firstElem = collection.iterator().next();
+        Class<?> componentType = firstElem.getClass();
+        return componentType;
+    }
+
+    private <T> void traversePrimitiveCollection(Object object, Field field,
+            Collection<T> collection) throws IllegalArgumentException, IllegalAccessException
+    {
+        Collection<T> newCollection = visitPrimitiveVisitableCollection(collection);
+        field.set(object, newCollection);
+    }
+
+    private <T> Collection<T> visitPrimitiveVisitableCollection(Collection<T> collection)
+    {
+        Collection<T> newCollection = createEmptyCollection(collection);
+        for (T element : collection)
+        {
+            T newElement = visitor.tryVisit(element);
+            newCollection.add(newElement != null ? newElement : element);
+        }
+        return newCollection;
+    }
+
+    // NOTE: works only for sets and lists
+    private static <T> Collection<T> createEmptyCollection(Collection<T> collection)
+    {
+        if (collection.getClass().isAssignableFrom(List.class))
+        {
+            return new ArrayList<T>();
+        } else if (collection.getClass().isAssignableFrom(Set.class))
+        {
+            return new HashSet<T>();
+        } else
+        {
+            throw new IllegalStateException("Do not know how to create a collection of type "
+                    + collection.getClass().getName());
+        }
+    }
+
+    private void traverseArray(Object array)
+    {
+        int length = Array.getLength(array);
+        Class<?> componentType = array.getClass().getComponentType();
+        boolean isPrimitive = componentType.isPrimitive();
+
+        for (int index = 0; index < length; ++index)
+        {
+            Object element = Array.get(array, index);
+            if (isPrimitive)
+            {
+                visitPrimitiveArrayElement(array, index, element, componentType);
+            } else
+            {
+                if (isCollection(element))
+                {
+                    Collection<?> collection = (Collection<?>) element;
+                    Class<?> primitiveElementType = tryGetPrimitiveElementType(collection);
+                    if (primitiveElementType != null)
+                    {
+                        visitPrimitiveCollectionArrayElement(array, index, collection,
+                                primitiveElementType);
+                    } else
+                    {
+                        traverseMutable(element, componentType);
+                    }
+                } else
+                {
+                    traverseMutable(element, componentType);
+                }
+            }
+        }
+    }
+
+    // array[index] contains collection of primitive types which will be modified if necessary
+    private void visitPrimitiveCollectionArrayElement(Object array, int index,
+            Collection<?> collection, Class<?> primitiveElementType)
+    {
+        if (visitor.isVisiting(primitiveElementType))
+        {
+            Collection newCollection = visitPrimitiveVisitableCollection(collection);
+            Array.set(array, index, newCollection);
+        }
+    }
+
+    // array[index] contains a value of primitive type which will be modified if necessary
+    private void visitPrimitiveArrayElement(Object array, int index, Object element,
+            Class<?> componentType)
+    {
+        if (visitor.isVisiting(componentType))
+        {
+            Object newElement = visitor.tryVisit(element);
+            if (newElement != null)
+            {
+                Array.set(array, index, newElement);
+            }
+        }
+    }
+
+    private Class<?> tryGetPrimitiveElementType(Collection<?> collection)
+    {
+        Class<?> elementClass = figureElementClass(collection);
+        if (elementClass.isPrimitive())
+        {
+            return elementClass;
+        } else
+        {
+            return null;
+        }
+    }
+
+    private static boolean isCollection(final Object o)
+    {
+        return o instanceof Collection<?>;
+    }
+}
-- 
GitLab