1 /*
2 * The basecode project
3 *
4 * Copyright (c) 2006 University of British Columbia
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19 package ubic.basecode.util;
20
21 import java.beans.BeanInfo;
22 import java.beans.IntrospectionException;
23 import java.beans.Introspector;
24 import java.beans.PropertyDescriptor;
25 import java.lang.reflect.InvocationTargetException;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Iterator;
29
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 /**
34 * Very simple class to produce String versions of beans and other objects.
35 * <p>
36 * For beans, the entire hierarchy of associations for each object is printed in a tree-like format. This is primarily
37 * used for testing.
38 *
39 * @author pavlidis
40 *
41 */
42 public class PrettyPrinter {
43
44 /**
45 * The maximum number of items to display from a collection.
46 */
47 private static final int MAX_TO_SHOW = 10;
48
49 private static Logger log = LoggerFactory.getLogger( PrettyPrinter.class );
50
51 /**
52 * Print out a collection of beans in a relatively pleasing format.
53 *
54 * @param packages collection of Strings for inclusion of classes for printing. E.g. "ubic.gemma" would print all
55 * classes in the ubic.gemma package (including subpackages). If empty, everything gets printed.
56 * @param beans Collection of beans.
57 * @return String representing the objects.
58 */
59 public static String print( Collection<String> packages, Collection<Object> beans ) {
60 StringBuffer buf = new StringBuffer();
61 try {
62 for ( Iterator<Object> iter = beans.iterator(); iter.hasNext(); ) {
63 Object gemmaObj = iter.next();
64
65 if ( gemmaObj == null ) log.error( "Null object in collection" );
66 print( packages, buf, gemmaObj );
67
68 }
69 } catch ( Exception e ) {
70 // just carry on.
71 }
72 return buf.toString();
73 }
74
75 /**
76 * Pretty-print a single bean. Beans that are not part of this project are ignored.
77 *
78 * @param packages collection of Strings for inclusion of classes for printing. E.g. "ubic.gemma" would print all
79 * classes in the ubic.gemma package (including subpackages). If empty, everything gets printed.
80 * @param object
81 * @return String representing the object.
82 */
83 public static String print( Collection<String> packages, Object object ) {
84 StringBuffer buf = new StringBuffer();
85
86 print( packages, buf, object );
87
88 return buf.toString();
89 }
90
91 /**
92 * Print out a collection of objects in a relatively pleasing format.
93 *
94 * @param packages collection of Strings for inclusion of classes for printing. E.g. "ubic.gemma" would print all
95 * classes in the ubic.gemma package (including subpackages). If empty, everything gets printed.
96 * @param beans Collection of beans.
97 * @return String representing the objects.
98 */
99 public static String print( Collection<String> packages, Object[] objects ) {
100 return print( packages, Arrays.asList( objects ) );
101 }
102
103 private static boolean include( Collection<String> packages, Class<?> clazz ) {
104 if ( packages == null || packages.isEmpty() ) return true;
105
106 for ( String string : packages ) {
107 if ( clazz.getName().startsWith( string ) ) return true;
108 }
109 return false;
110
111 }
112
113 /**
114 * @param packages
115 * @param buf
116 * @param gemmeCollection
117 * @param level
118 */
119 private static void print( Collection<String> packages, StringBuffer buf, Collection<?> gemmaCollection, int level ) {
120 int i = 0;
121 for ( Object gemmaObj : gemmaCollection ) {
122 print( packages, buf, gemmaObj, level );
123 i++;
124 if ( i >= MAX_TO_SHOW ) {
125 buf.append( "..." + ( gemmaCollection.size() - MAX_TO_SHOW ) + " more "
126 + gemmaObj.getClass().getSimpleName() + "'s\n" );
127 break;
128 }
129 }
130 }
131
132 /**
133 * @param buf
134 * @param gemmaObj
135 * @throws IntrospectionException
136 * @throws IllegalArgumentException
137 * @throws IllegalAccessException
138 * @throws InvocationTargetException
139 */
140 private static void print( Collection<String> packages, StringBuffer buf, Object gemmaObj ) {
141 print( packages, buf, gemmaObj, 0 );
142 }
143
144 /**
145 * The only class that does any real work. Recursively print an object and all its associated objects.
146 *
147 * @throws InvocationTargetException
148 * @throws IllegalAccessException
149 * @throws IllegalArgumentException
150 * @throws IntrospectionException
151 * @param packages collection of Strings for inclusion of classes for printing. E.g. "ubic.gemma" would print all
152 * classes in the ubic.gemma package (including subpackages). If empty, everything gets printed.
153 * @param buf
154 * @param gemmaObj
155 * @param level Used to track indents.
156 */
157 private static void print( Collection<String> packages, StringBuffer buf, Object bean, int level ) {
158
159 if ( bean == null ) return;
160 Class<?> clazz = bean.getClass();
161
162 if ( bean instanceof Collection ) {
163 print( packages, buf, ( Collection<?> ) bean, ++level );
164 return;
165 }
166
167 if ( !include( packages, clazz ) ) {
168 return;
169 }
170 BeanInfo bif;
171 try {
172 bif = Introspector.getBeanInfo( clazz );
173 } catch ( Exception e ) {
174 return;
175 }
176 PropertyDescriptor[] props = bif.getPropertyDescriptors();
177
178 StringBuffer indent = new StringBuffer();
179 for ( int i = 0; i < level; i++ )
180 indent.append( " " );
181
182 boolean first = true;
183 level++;
184
185 for ( int i = 0; i < props.length; i++ ) {
186 PropertyDescriptor prop = props[i];
187
188 Object o;
189 try {
190 o = prop.getReadMethod().invoke( bean, new Object[] {} );
191 } catch ( Exception e ) {
192 continue;
193 }
194
195 if ( prop.getDisplayName().equals( "class" ) ) continue; // everybody has it.
196 if ( prop.getDisplayName().equals( "mutable" ) ) continue; // shows up in the enums, just clutter.
197
198 // generate a 'heading' for this object.
199 if ( first ) buf.append( indent + bean.getClass().getSimpleName() + " Properties:\n" );
200
201 first = false;
202 buf.append( indent + " " + bean.getClass().getSimpleName() + "." + prop.getName() + ": "
203 + ( o == null ? "---" : o ) + "\n" );
204 print( packages, buf, o, level );
205 }
206 }
207
208 private PrettyPrinter() {
209 }
210 }