001 /*
002 * $Id: ObjectInspector.java,v 1.10 2013/12/19 21:53:57 oboehm Exp $
003 *
004 * Copyright (c) 2012 by Oliver Boehm
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *
018 * (c)reated 24.01.2012 by oliver (ob@oasd.de)
019 */
020
021 package patterntesting.runtime.util;
022
023 import java.io.*;
024 import java.lang.reflect.Field;
025 import java.util.*;
026 import java.util.regex.Pattern;
027
028 import org.slf4j.*;
029
030 /**
031 * This class lets you examine an object and dump its internal attributes.
032 * It was introduced to find some secrets of IBM's classloader.
033 *
034 * @author oboehm
035 * @since 1.2.10-YEARS (24.01.2012)
036 */
037 public final class ObjectInspector {
038
039 /** The value for "nothing found". */
040 public static final String NOTHING_FOUND = null;
041
042 private static final Logger log = LoggerFactory.getLogger(ObjectInspector.class);
043 private final Object inspected;
044 private final Set<Object> visited = new HashSet<Object>();
045
046 /**
047 * Instantiates a new object inspector.
048 *
049 * @param obj the object to be inspected
050 */
051 public ObjectInspector(final Object obj) {
052 this.inspected = obj;
053 }
054
055 /**
056 * Gets the type (class) of the stored object.
057 *
058 * @return the type
059 */
060 public Class<?> getType() {
061 return this.inspected.getClass();
062 }
063
064 /**
065 * Find the value in of the attributes of the inspected object. You can
066 * give a {@link Pattern} as parameter if you want to use wildcards for
067 * the search.
068 *
069 * @param value the value or the pattern you want to look for
070 * @return the string
071 * @throws ValueNotFoundException the value not found exception
072 */
073 public String findValue(final Object value) throws ValueNotFoundException {
074 try {
075 return findValue(value, this.inspected, this.inspected.getClass().getName());
076 } finally {
077 this.visited.clear();
078 }
079 }
080
081 private String findValue(final Object value, final Object where, final String path)
082 throws ValueNotFoundException {
083 for (Field field : getAllFields(where.getClass())) {
084 field.setAccessible(true);
085 try {
086 Object obj = field.get(where);
087 String fieldPath = path + "." + field.getName();
088 if (isEquals(obj, value)) {
089 return fieldPath;
090 }
091 if (alreadyVisited(obj)) {
092 continue;
093 }
094 if (isArrayType(obj)) {
095 return findArrayValue(value, obj, fieldPath);
096 } else if (isIterable(obj)) {
097 return findValue(value, getIterator(obj), fieldPath);
098 } else if (isComplexType(obj)) {
099 try {
100 return findValue(value, obj, fieldPath);
101 } catch (ValueNotFoundException vnfe) {
102 log.trace("value not found in {}.{}", path, field);
103 }
104 }
105 } catch (IllegalAccessException e) {
106 throw new IllegalArgumentException("can't access " + field + " in "
107 + where.getClass());
108 }
109 }
110 throw new ValueNotFoundException(value);
111 }
112
113 private String findArrayValue(final Object value, final Object where, final String path)
114 throws ValueNotFoundException {
115 try {
116 Object[] objects = (Object[]) where;
117 for (int i = 0; i < objects.length; i++) {
118 String arrayPath = path + "[" + i + "]";
119 if (isEquals(value, objects[i])) {
120 return arrayPath;
121 }
122 if (objects[i] == null) {
123 continue;
124 }
125 try {
126 return findValue(value, objects[i], path + ".");
127 } catch (ValueNotFoundException vnfe) {
128 log.trace("value not found in {}.", arrayPath);
129 }
130 }
131 } catch (ClassCastException cce) {
132 log.trace("Do not look in native array {} for {}.", where, value);
133 }
134 throw new ValueNotFoundException(value);
135 }
136
137 private String findValue(final Object value, final Iterator<?> where, final String path)
138 throws ValueNotFoundException {
139 int i = 0;
140 while (where.hasNext()) {
141 Object obj = where.next();
142 String arrayPath = path + "[" + i + "]";
143 if (isEquals(value, obj)) {
144 return arrayPath;
145 }
146 try {
147 return findValue(value, obj, arrayPath);
148 } catch (ValueNotFoundException vnfe) {
149 log.trace("value not found in {}.", arrayPath);
150 }
151 }
152 throw new ValueNotFoundException(value);
153 }
154
155 private static boolean isEquals(final Object one, final Object two) {
156 if (one == null) {
157 return two == null;
158 }
159 if (two == null) {
160 return false;
161 }
162 if (one.equals(two)) {
163 return true;
164 }
165 try {
166 Pattern pattern = (Pattern) one;
167 return pattern.matcher(two.toString()).matches();
168 } catch (ClassCastException cce) {
169 return false;
170 }
171 }
172
173 /**
174 * Gets all fields of the wrapped object. Not only the (public) class
175 * fields but also the private and protected fields of the superclass.
176 *
177 * @return the all fields
178 */
179 public Collection<Field> getAllFields() {
180 return getAllFields(this.inspected.getClass());
181 }
182
183 private static Collection<Field> getAllFields(final Class<?> clazz) {
184 Collection<Field> fields = new ArrayList<Field>();
185 addFields(fields, clazz);
186 return fields;
187 }
188
189 private static void addFields(final Collection<Field> fields, final Class<?> clazz) {
190 Class<?> superclass = clazz.getSuperclass();
191 if (superclass != null) {
192 addFields(fields, superclass);
193 }
194 fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
195 }
196
197 /**
198 * Dump the inspected class.
199 *
200 * @param writer the writer
201 * @throws IOException Signals that an I/O exception has occurred.
202 */
203 public synchronized void dump(final Writer writer) throws IOException {
204 writer.append("=== Dump of " + this.inspected.getClass() + " ===\n");
205 dump(writer, this.inspected, this.inspected.getClass().getName());
206 this.visited.clear();
207 }
208
209 private void dump(final Writer writer, final Object obj, final String prefix)
210 throws IOException {
211 if (obj == null) {
212 writer.append(prefix + "(null)\n");
213 return;
214 }
215 Collection<Field> fields = getAllFields(obj.getClass());
216 for (Field field : fields) {
217 dump(writer, field, obj, prefix);
218 try {
219 Object value = field.get(obj);
220 if ((value == null) || alreadyVisited(value)) {
221 continue;
222 }
223 if (isArrayType(value)) {
224 dumpArray(writer, value, prefix);
225 } else if (isIterable(value)) {
226 try {
227 Iterator<?> iterator = getIterator(value);
228 int i = 0;
229 while (iterator.hasNext()) {
230 Object next = iterator.next();
231 dump(writer, next, prefix + "[" + i + "]");
232 i++;
233 }
234 } catch (ConcurrentModificationException cme) {
235 log.warn("Houston, we have a problem with iterator of " + value, cme);
236 writer.append(prefix + "[..]");
237 writer.append(" = ??? (" + cme + ")\n");
238 ThreadUtil.sleep();
239 }
240 } else if (isComplexType(value)) {
241 dump(writer, value, prefix + "." + field.getName());
242 }
243 } catch (IllegalAccessException iae) {
244 throw new IllegalArgumentException("can't access " + field, iae);
245 }
246 }
247 }
248
249 private static void dump(final Writer writer, final Field field, final Object obj,
250 final String prefix) throws IOException {
251 field.setAccessible(true);
252 writer.append(prefix);
253 writer.append('.');
254 writer.append(field.getName());
255 writer.append(" = (");
256 writer.append(field.getType().getSimpleName());
257 writer.append(") ");
258 try {
259 writer.append(Converter.toString(field.get(obj)));
260 } catch (IllegalAccessException iae) {
261 log.debug("can't access field {}", field, iae);
262 writer.append("??? (");
263 writer.append(iae.getLocalizedMessage());
264 writer.append(")");
265 }
266 writer.append("\n");
267 }
268
269 private void dumpArray(final Writer writer, final Object value, final String prefix)
270 throws IOException {
271 try {
272 Object[] array = (Object[]) value;
273 for (int i = 0; i < array.length; i++) {
274 dump(writer, array[i], prefix + "[" + i + "]");
275 }
276 } catch (ClassCastException cce) {
277 log.trace("Native array {} is not dumped with each element.", value);
278 }
279 }
280
281 /**
282 * Checks if the wrapped object is complex type.
283 *
284 * @return true, if is complex type
285 */
286 public boolean isComplexType() {
287 return isComplexType(this.inspected);
288 }
289
290 /**
291 * Checks if the given object is of complex type. These are all types which
292 * <ul>
293 * <li>are not a primitive type (like int, char, ...)</li>
294 * <li>are not a String class</li>
295 * <li>are not of subtype Numer (like Long, Short, ...)</li>
296 * </ul>
297 *
298 * @param obj the obj
299 * @return true, if is complex type
300 */
301 public static boolean isComplexType(final Object obj) {
302 if (obj == null) {
303 return false;
304 }
305 Class<?> clazz = obj.getClass();
306 if (clazz.isPrimitive()) {
307 return false;
308 }
309 if (String.class.equals(clazz)) {
310 return false;
311 }
312 if (Number.class.isAssignableFrom(clazz)) {
313 return false;
314 }
315 if (Character.class.isAssignableFrom(clazz)) {
316 return false;
317 }
318 return true;
319 }
320
321 /**
322 * Checks if the wrapped object is an array type.
323 *
324 * @return true, if is array type
325 */
326 public boolean isArrayType() {
327 return isArrayType(this.inspected);
328 }
329
330 /**
331 * Checks if the given object is an array.
332 *
333 * @param obj the obj
334 * @return true, if is array type
335 */
336 public static boolean isArrayType(final Object obj) {
337 if (obj == null) {
338 return false;
339 }
340 Class<?> clazz = obj.getClass();
341 return clazz.isArray();
342 }
343
344 /**
345 * Checks if the type of the wrapped object could be iterated. This is the
346 * case e.g. for Collections and its subclasses.
347 *
348 * @return true, if is iterable
349 */
350 public boolean isIterable() {
351 return isIterable(this.inspected);
352 }
353
354 /**
355 * Checks if the type of the given object could be iterated. This is the
356 * case e.g. for Collections and its subclasses.
357 *
358 * @param obj the object
359 * @return true, if is iterable
360 */
361 public static boolean isIterable(final Object obj) {
362 if (obj == null) {
363 return false;
364 }
365 Class<?> clazz = obj.getClass();
366 try {
367 clazz.getMethod("iterator");
368 return true;
369 } catch (SecurityException e) {
370 throw new IllegalArgumentException("can't access methods for " + clazz, e);
371 } catch (NoSuchMethodException e) {
372 return false;
373 }
374 }
375
376 private static Iterator<?> getIterator(final Object value) {
377 return (Iterator<?>) ReflectionHelper.invokeMethod(value, "iterator");
378 }
379
380 private boolean alreadyVisited(final Object value) {
381 try {
382 if (visited.contains(value)) {
383 return true;
384 }
385 visited.add(value);
386 } catch (RuntimeException ex) {
387 log.debug("can't store \"{}\"", value, ex);
388 }
389 return false;
390 }
391
392 /**
393 * Dumps all attributes of the inspected object in the form
394 * <code>attribute=value</code>.
395 *
396 * @return the string
397 * @see java.lang.Object#toString()
398 */
399 @Override
400 public String toString() {
401 return this.getClass().getSimpleName() + " for " + this.inspected.getClass();
402 }
403
404 /**
405 * To long string.
406 *
407 * @return the string
408 */
409 public String toLongString() {
410 StringBuilder buffer = new StringBuilder();
411 Class<?> clazz = this.inspected.getClass();
412 while(clazz != null) {
413 buffer.insert(0, toString(clazz.getDeclaredFields()));
414 clazz = clazz.getSuperclass();
415 }
416 return buffer.toString();
417 }
418
419 private String toString(final Field[] fields) {
420 return toString(fields, this.inspected);
421 }
422
423 private static String toString(final Field[] fields, final Object obj) {
424 StringWriter buffer = new StringWriter();
425 try {
426 for (int i = 0; i < fields.length; i++) {
427 dump(buffer, fields[i], obj, "");
428 }
429 buffer.close();
430 } catch (IOException canthappen) {
431 log.info("I have some problems dumping fields {}.", fields, canthappen);
432 }
433 return buffer.toString();
434 }
435
436 }
437