View Javadoc
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 }