All Downloads are FREE. Search and download functionalities are using the official Maven repository.

patterntesting.runtime.util.ObjectInspector Maven / Gradle / Ivy

Go to download

PatternTesting Runtime (patterntesting-rt) is the runtime component for the PatternTesting framework. It provides the annotations and base classes for the PatternTesting testing framework (e.g. patterntesting-check, patterntesting-concurrent or patterntesting-exception) but can be also used standalone for classpath monitoring or profiling. It uses AOP and AspectJ to perform this feat.

There is a newer version: 2.4.0
Show newest version
/*
 * $Id: ObjectInspector.java,v 1.10 2013/12/19 21:53:57 oboehm Exp $
 *
 * Copyright (c) 2012 by Oliver Boehm
 *
 * 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 orimplied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * (c)reated 24.01.2012 by oliver ([email protected])
 */

package patterntesting.runtime.util;

import java.io.*;
import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Pattern;

import org.slf4j.*;

/**
 * This class lets you examine an object and dump its internal attributes.
 * It was introduced to find some secrets of IBM's classloader.
 *
 * @author oboehm
 * @since 1.2.10-YEARS (24.01.2012)
 */
public final class ObjectInspector {

    /** The value for "nothing found". */
    public static final String NOTHING_FOUND = null;

    private static final Logger log = LoggerFactory.getLogger(ObjectInspector.class);
    private final Object inspected;
    private final Set visited = new HashSet();

    /**
     * Instantiates a new object inspector.
     *
     * @param obj the object to be inspected
     */
    public ObjectInspector(final Object obj) {
        this.inspected = obj;
    }

    /**
     * Gets the type (class) of the stored object.
     *
     * @return the type
     */
    public Class getType() {
        return this.inspected.getClass();
    }

    /**
     * Find the value in of the attributes of the inspected object. You can
     * give a {@link Pattern} as parameter if you want to use wildcards for
     * the search.
     *
     * @param value the value or the pattern you want to look for
     * @return the string
     * @throws ValueNotFoundException the value not found exception
     */
    public String findValue(final Object value) throws ValueNotFoundException {
        try {
            return findValue(value, this.inspected, this.inspected.getClass().getName());
        } finally {
            this.visited.clear();
        }
    }

    private String findValue(final Object value, final Object where, final String path)
            throws ValueNotFoundException {
        for (Field field : getAllFields(where.getClass())) {
            field.setAccessible(true);
            try {
                Object obj = field.get(where);
                String fieldPath = path + "." + field.getName();
                if (isEquals(obj, value)) {
                    return fieldPath;
                }
                if (alreadyVisited(obj)) {
                    continue;
                }
                if (isArrayType(obj)) {
                    return findArrayValue(value, obj, fieldPath);
                } else if (isIterable(obj)) {
                    return findValue(value, getIterator(obj), fieldPath);
                } else if (isComplexType(obj)) {
                    try {
                        return findValue(value, obj, fieldPath);
                    } catch (ValueNotFoundException vnfe) {
                        log.trace("value not found in {}.{}", path, field);
                    }
                }
            } catch (IllegalAccessException e) {
                throw new IllegalArgumentException("can't access " + field + " in "
                        + where.getClass());
            }
        }
        throw new ValueNotFoundException(value);
    }

    private String findArrayValue(final Object value, final Object where, final String path)
            throws ValueNotFoundException {
        try {
            Object[] objects = (Object[]) where;
            for (int i = 0; i < objects.length; i++)  {
                String arrayPath = path + "[" + i + "]";
                if (isEquals(value, objects[i])) {
                    return arrayPath;
                }
                if (objects[i] == null) {
                    continue;
                }
                try {
                    return findValue(value, objects[i], path + ".");
                } catch (ValueNotFoundException vnfe) {
                    log.trace("value not found in {}.", arrayPath);
                }
            }
        } catch (ClassCastException cce) {
            log.trace("Do not look in native array {} for {}.", where, value);
        }
        throw new ValueNotFoundException(value);
    }

    private String findValue(final Object value, final Iterator where, final String path)
            throws ValueNotFoundException {
        int i = 0;
        while (where.hasNext()) {
            Object obj = where.next();
            String arrayPath = path + "[" + i + "]";
            if (isEquals(value, obj)) {
                return arrayPath;
            }
            try {
                return findValue(value, obj, arrayPath);
            } catch (ValueNotFoundException vnfe) {
                log.trace("value not found in {}.", arrayPath);
            }
        }
        throw new ValueNotFoundException(value);
    }

    private static boolean isEquals(final Object one, final Object two) {
        if (one == null) {
            return two == null;
        }
        if (two == null) {
            return false;
        }
        if (one.equals(two)) {
            return true;
        }
        try {
            Pattern pattern = (Pattern) one;
            return pattern.matcher(two.toString()).matches();
        } catch (ClassCastException cce) {
            return false;
        }
    }

    /**
     * Gets all fields of the wrapped object. Not only the (public) class
     * fields but also the private and protected fields of the superclass.
     *
     * @return the all fields
     */
    public Collection getAllFields() {
        return getAllFields(this.inspected.getClass());
    }

    private static Collection getAllFields(final Class clazz) {
        Collection fields = new ArrayList();
        addFields(fields, clazz);
        return fields;
    }

    private static void addFields(final Collection fields, final Class clazz) {
        Class superclass = clazz.getSuperclass();
        if (superclass != null) {
            addFields(fields, superclass);
        }
        fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
    }

    /**
     * Dump the inspected class.
     *
     * @param writer the writer
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public synchronized void dump(final Writer writer) throws IOException {
        writer.append("=== Dump of " + this.inspected.getClass() + " ===\n");
        dump(writer, this.inspected, this.inspected.getClass().getName());
        this.visited.clear();
    }

    private void dump(final Writer writer, final Object obj, final String prefix)
            throws IOException {
        if (obj == null) {
            writer.append(prefix + "(null)\n");
            return;
        }
        Collection fields = getAllFields(obj.getClass());
        for (Field field : fields) {
            dump(writer, field, obj, prefix);
            try {
                Object value = field.get(obj);
                if ((value == null) || alreadyVisited(value)) {
                    continue;
                }
                if (isArrayType(value)) {
                    dumpArray(writer, value, prefix);
                } else if (isIterable(value)) {
                    try {
                        Iterator iterator = getIterator(value);
                        int i = 0;
                        while (iterator.hasNext()) {
                            Object next = iterator.next();
                            dump(writer, next, prefix + "[" + i + "]");
                            i++;
                        }
                    } catch (ConcurrentModificationException cme) {
                        log.warn("Houston, we have a problem with iterator of " + value, cme);
                        writer.append(prefix + "[..]");
                        writer.append(" = ??? (" + cme + ")\n");
                        ThreadUtil.sleep();
                    }
                } else if (isComplexType(value)) {
                    dump(writer, value, prefix + "." + field.getName());
                }
            } catch (IllegalAccessException iae) {
                throw new IllegalArgumentException("can't access " + field, iae);
            }
        }
    }

    private static void dump(final Writer writer, final Field field, final Object obj,
            final String prefix) throws IOException {
        field.setAccessible(true);
        writer.append(prefix);
        writer.append('.');
        writer.append(field.getName());
        writer.append(" = (");
        writer.append(field.getType().getSimpleName());
        writer.append(") ");
        try {
            writer.append(Converter.toString(field.get(obj)));
        } catch (IllegalAccessException iae) {
            log.debug("can't access field {}", field, iae);
            writer.append("??? (");
            writer.append(iae.getLocalizedMessage());
            writer.append(")");
        }
        writer.append("\n");
    }

    private void dumpArray(final Writer writer, final Object value, final String prefix)
            throws IOException {
        try {
            Object[] array = (Object[]) value;
            for (int i = 0; i < array.length; i++) {
                dump(writer, array[i], prefix + "[" + i + "]");
            }
        } catch (ClassCastException cce) {
            log.trace("Native array {} is not dumped with each element.", value);
        }
    }

    /**
     * Checks if the wrapped object is complex type.
     *
     * @return true, if is complex type
     */
    public boolean isComplexType() {
        return isComplexType(this.inspected);
    }

    /**
     * Checks if the given object is of complex type. These are all types which
     * 
    *
  • are not a primitive type (like int, char, ...)
  • *
  • are not a String class
  • *
  • are not of subtype Numer (like Long, Short, ...)
  • *
* * @param obj the obj * @return true, if is complex type */ public static boolean isComplexType(final Object obj) { if (obj == null) { return false; } Class clazz = obj.getClass(); if (clazz.isPrimitive()) { return false; } if (String.class.equals(clazz)) { return false; } if (Number.class.isAssignableFrom(clazz)) { return false; } if (Character.class.isAssignableFrom(clazz)) { return false; } return true; } /** * Checks if the wrapped object is an array type. * * @return true, if is array type */ public boolean isArrayType() { return isArrayType(this.inspected); } /** * Checks if the given object is an array. * * @param obj the obj * @return true, if is array type */ public static boolean isArrayType(final Object obj) { if (obj == null) { return false; } Class clazz = obj.getClass(); return clazz.isArray(); } /** * Checks if the type of the wrapped object could be iterated. This is the * case e.g. for Collections and its subclasses. * * @return true, if is iterable */ public boolean isIterable() { return isIterable(this.inspected); } /** * Checks if the type of the given object could be iterated. This is the * case e.g. for Collections and its subclasses. * * @param obj the object * @return true, if is iterable */ public static boolean isIterable(final Object obj) { if (obj == null) { return false; } Class clazz = obj.getClass(); try { clazz.getMethod("iterator"); return true; } catch (SecurityException e) { throw new IllegalArgumentException("can't access methods for " + clazz, e); } catch (NoSuchMethodException e) { return false; } } private static Iterator getIterator(final Object value) { return (Iterator) ReflectionHelper.invokeMethod(value, "iterator"); } private boolean alreadyVisited(final Object value) { try { if (visited.contains(value)) { return true; } visited.add(value); } catch (RuntimeException ex) { log.debug("can't store \"{}\"", value, ex); } return false; } /** * Dumps all attributes of the inspected object in the form * attribute=value. * * @return the string * @see java.lang.Object#toString() */ @Override public String toString() { return this.getClass().getSimpleName() + " for " + this.inspected.getClass(); } /** * To long string. * * @return the string */ public String toLongString() { StringBuilder buffer = new StringBuilder(); Class clazz = this.inspected.getClass(); while(clazz != null) { buffer.insert(0, toString(clazz.getDeclaredFields())); clazz = clazz.getSuperclass(); } return buffer.toString(); } private String toString(final Field[] fields) { return toString(fields, this.inspected); } private static String toString(final Field[] fields, final Object obj) { StringWriter buffer = new StringWriter(); try { for (int i = 0; i < fields.length; i++) { dump(buffer, fields[i], obj, ""); } buffer.close(); } catch (IOException canthappen) { log.info("I have some problems dumping fields {}.", fields, canthappen); } return buffer.toString(); } }