001 package com.sptci.echo2;
002
003 import java.io.IOException;
004
005 import java.lang.reflect.Field;
006
007 import java.util.ArrayList;
008 import java.util.Date;
009 import java.util.Formatter;
010 import java.util.List;
011 import java.util.Map;
012 import java.util.TreeMap;
013
014 import java.text.SimpleDateFormat;
015
016 import org.rakeshv.utils.StringUtilities;
017
018 import com.sptci.CodeGenerator;
019 import com.sptci.ReflectionUtility;
020
021 /**
022 * A JavaBean generator. Uses reflection to create a JavaBean that
023 * maps to the UI component fields in the specified class.
024 *
025 * <p>The following shows the invocation semantics for this class:</p>
026 * <pre>
027 * java -classpath <path to classes>:sptecho.jar:<path to Echo2_App.jar> \
028 * com.sptci.echo2.ModelGenerator \
029 * <fully qualified name of UI class>
030 * </pre>
031 *
032 * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
033 * @author Rakesh Vidyadharan 2006-02-07
034 * @version $Id: ModelGenerator.java,v 1.3 2006/02/15 00:48:26 rakesh Exp $
035 */
036 public class ModelGenerator extends CodeGenerator
037 {
038 /**
039 * The class for which the JavaBean is to be generated.
040 */
041 protected Class source;
042
043 /**
044 * A <code>List</code> of import statements for the generated class.
045 */
046 protected List<String> imports;
047
048 /**
049 * A <code>Map</code> of <code>fields</code> in {@link #source}.
050 */
051 protected Map<String, Field> uiFields;
052
053 /**
054 * Instantiates a new instance of {@link #source} from the specified
055 * <code>name</code>. Initialises code geneartor containers.
056 *
057 * @param name The fully qualified name of the class. Example:
058 * <code>com.sptci.demo.InputForm</code>.
059 * @throws ClassNotFoundException If the class corresponding to the
060 * <code>name</code> cannot be found in the classpath.
061 */
062 public ModelGenerator( String name ) throws ClassNotFoundException
063 {
064 super();
065 source = Class.forName( name );
066
067 this.name = source.getName().substring(
068 source.getName().lastIndexOf( "." ) + 1 ) + "Model";
069 imports = new ArrayList<String>();
070 imports.add( "java.beans.PropertyChangeListener" );
071 imports.add( "java.beans.PropertyChangeSupport" );
072 imports.add( "java.io.Serializable" );
073 imports.add( "java.util.Formatter" );
074 uiFields = new TreeMap<String, Field>();
075 }
076
077 /**
078 * Load the class specified, and generate a JavaBean representing its
079 * UI components.
080 *
081 * @see #processComponent
082 * @see #generateSource
083 * @throws Exception If errors are encountered while writing to
084 * the source file.
085 */
086 @Override
087 public void generate() throws Exception
088 {
089 for ( Map.Entry<String, Field> entry :
090 ReflectionUtility.fetchFields( source ).entrySet() )
091 {
092 Field field = entry.getValue();
093 try
094 {
095 source.asSubclass( nextapp.echo2.app.Component.class );
096 uiFields.put( field.getName(), field );
097 processComponent( field );
098 }
099 catch ( ClassCastException ccex ) {}
100 }
101
102 generateSource();
103 }
104
105 /**
106 * Generate a <code>Field</code> for the JavaBean class that will be
107 * generated from the specified Echo2 UI component
108 *
109 * @param field The <code>Field</code> that represents the
110 * Echo2 component.
111 */
112 protected void processComponent( Field field )
113 {
114 try
115 {
116 field.getType().asSubclass(
117 nextapp.echo2.app.text.TextComponent.class );
118 fields.put( field.getName(), "String" );
119 }
120 catch ( ClassCastException ccex ) {}
121
122 try
123 {
124 field.getType().asSubclass(
125 java.util.Map.class );
126
127 if ( ! imports.contains( "java.util.Map" ) )
128 {
129 imports.add( "java.util.Map" );
130 imports.add( "java.util.TreeMap" );
131 }
132
133 fields.put( field.getName(), "Map<String, Boolean>" );
134 }
135 catch ( ClassCastException ccex ) {}
136
137 try
138 {
139 field.getType().asSubclass(
140 nextapp.echo2.app.list.AbstractListComponent.class );
141
142 if ( ! imports.contains( "java.util.ArrayList" ) )
143 {
144 imports.add( "java.util.ArrayList" );
145 imports.add( "java.util.List" );
146 imports.add( "com.sptci.echo2.ListItem" );
147 }
148
149 fields.put( field.getName(), "List<ListItem>" );
150 }
151 catch ( ClassCastException ccex ) {}
152 }
153
154 /**
155 * Generate the source code for the JavaBean.
156 *
157 * @throws IOException If errors are encountered while writing the
158 * generated source file.
159 */
160 protected void generateSource() throws IOException
161 {
162 StringBuilder builder = new StringBuilder( 4096 );
163 Formatter formatter = new Formatter( builder );
164
165 formatter.format( "package %s;%n%n", source.getPackage().getName() );
166
167 for ( String name : imports )
168 {
169 formatter.format( "import %s;%n", name );
170 }
171 formatter.format( "%n" );
172
173 formatter.format( "/**%n" );
174 formatter.format(
175 " * A JavaBean that represents the data represented by %n" );
176 formatter.format( " * {@link %s}.%n", source.getName() );
177 formatter.format( " *%n" );
178 formatter.format( " * @author %s %s%n", getClass().getName(),
179 ( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ssZ" ) ).format( new Date() ) );
180 formatter.format( " * @version $Id: ModelGenerator.java,v 1.3 2006/02/15 00:48:26 rakesh Exp $%n" );
181 formatter.format( " */%n" );
182 formatter.format( "public class %s%n", name );
183 formatter.format( " implements Cloneable, Comparable<%s>, Serializable%n",
184 name );
185 formatter.format( "{%n" );
186
187 builder.append( generateFields() );
188 builder.append( generateConstructors() );
189 builder.append( generateMethods() );
190
191 formatter.format( "}%n" );
192
193 StringUtilities.toFile( builder.toString(), name + ".java" );
194 }
195
196 /**
197 * Generate the fields for the JavaBean. Over-ridden to add code
198 * specific to the JavaBean.
199 *
200 * @see #generatePropertyChangeFields
201 * @return String The field declarations for the JavaBean.
202 */
203 protected String generateFields()
204 {
205 StringBuilder builder = new StringBuilder( 4096 );
206 Formatter formatter = new Formatter( builder );
207
208 builder.append( super.generateFields() );
209
210 for ( Map.Entry<String, String> entry : fields.entrySet() )
211 {
212 formatter.format( " /**%n" );
213 formatter.format(
214 " * Field that represents the %s field of type%n",
215 entry.getKey() );
216 formatter.format( " * %s%n",
217 uiFields.get( entry.getKey() ).getName() );
218 formatter.format( " */%n" );
219 formatter.format( " private %s %s;%n%n",
220 entry.getValue(), entry.getKey() );
221 }
222
223 builder.append( generatePropertyChangeFields() );
224
225 return builder.toString();
226 }
227
228 /**
229 * Generate the fields necessary to support PropertyChangeListener
230 * ability for the bean.
231 *
232 * @return String The field declarations necessary for property
233 * change support
234 */
235 protected String generatePropertyChangeFields()
236 {
237 StringBuilder builder = new StringBuilder( 512 );
238 Formatter formatter = new Formatter( builder );
239
240 formatter.format( " /**%n" );
241 formatter.format( " * The property change event dispatcher. This object%n" );
242 formatter.format( " * is lazily loaded.%n" );
243 formatter.format( " */%n" );
244 formatter.format( " private transient PropertyChangeSupport propertyChangeSupport;%n%n" );
245
246 return builder.toString();
247 }
248
249 /**
250 * Generate the constructors for the JavaBean.
251 *
252 * @see #generateDefaultConstructor
253 * @see #generateDesignatedConstructor
254 */
255 protected String generateConstructors()
256 {
257 StringBuilder builder = new StringBuilder( 4096 );
258
259 builder.append( generateDefaultConstructor() );
260 builder.append( generateDesignatedConstructor() );
261
262 return builder.toString();
263 }
264
265 /**
266 * Generate the default constructor for the JavaBean
267 */
268 protected String generateDefaultConstructor()
269 {
270 StringBuilder builder = new StringBuilder( 1024 );
271 Formatter formatter = new Formatter( builder );
272
273 formatter.format( " /**%n" );
274 formatter.format( " * Default constructor. Uses the designated constructor.%n" );
275 formatter.format( " */%n" );
276 formatter.format( " public %s()%n", name );
277 formatter.format( " {%n" );
278 formatter.format( " this( " );
279
280 int count = 0;
281 for ( Map.Entry<String, String> entry : fields.entrySet() )
282 {
283 if ( count != 0 )
284 {
285 formatter.format( ",%n " );
286 }
287
288 if ( entry.getValue().equals( "Map<String, Boolean>" ) )
289 {
290 formatter.format( " new Tree%s()", entry.getValue() );
291 }
292 else if ( entry.getValue().equals( "List<ListItem>" ) )
293 {
294 formatter.format( " new Array%s()", entry.getValue() );
295 }
296 else
297 {
298 formatter.format( " null" );
299 }
300
301 ++count;
302 }
303 formatter.format( " );%n" );
304 formatter.format( " }%n%n" );
305
306 return builder.toString();
307 }
308
309 /**
310 * Generate the designated constructor for the JavaBean
311 */
312 protected String generateDesignatedConstructor()
313 {
314 StringBuilder builder = new StringBuilder( 1024 );
315 Formatter formatter = new Formatter( builder );
316
317 formatter.format( " /**%n" );
318 formatter.format( " * Designated constructor. Initialises the%n" );
319 formatter.format( " * fields with the specified values.%n" );
320 formatter.format( " */%n" );
321 formatter.format( " public %s( ", name );
322
323 int count = 0;
324 for ( Map.Entry<String, String> entry : fields.entrySet() )
325 {
326 if ( count != 0 )
327 {
328 formatter.format( ",%n " );
329 }
330
331 formatter.format( "%s %s", entry.getValue(), entry.getKey() );
332 ++count;
333 }
334 formatter.format( " )%n" );
335
336 formatter.format( " {%n" );
337 for ( Map.Entry<String, String> entry : fields.entrySet() )
338 {
339 formatter.format( " set%s%s( %s );%n",
340 entry.getKey().substring( 0, 1 ).toUpperCase(),
341 entry.getKey().substring( 1 ),
342 entry.getKey() );
343 }
344 formatter.format( " }%n%n" );
345
346 return builder.toString();
347 }
348
349 /**
350 * Generate the methods for the JavaBean.
351 *
352 * @see #generateToStringMethod
353 * @see #generateEqualsMethod
354 * @see #generateHashCodeMethod
355 * @see #generateCompareToMethod
356 * @see #generateCloneMethod
357 * @see #generatePropertyChangeMethods
358 * @see #generateBeanMethods
359 * @return String The source code for the methods to be included in
360 * the JavaBean.
361 */
362 protected String generateMethods()
363 {
364 StringBuilder builder = new StringBuilder( 4096 );
365 builder.append( generateToStringMethod() );
366 builder.append( generateEqualsMethod() );
367 builder.append( generateHashCodeMethod() );
368 builder.append( generateCompareToMethod() );
369 builder.append( generateCloneMethod() );
370 builder.append( generatePropertyChangeMethods() );
371 builder.append( generateBeanMethods() );
372
373 return builder.toString();
374 }
375
376 /**
377 * Generate the methods to support property change events.
378 *
379 * @return String The source code for property change event
380 * handling.
381 */
382 protected String generatePropertyChangeMethods()
383 {
384 StringBuilder builder = new StringBuilder( 1024 );
385 Formatter formatter = new Formatter( builder );
386
387 formatter.format( " /**%n" );
388 formatter.format( " * Adds a property change listener to this <code>bean</code>.%n" );
389 formatter.format( " * Delegates handling to {@link #propertyChangeSupport}%n" );
390 formatter.format( " *%n" );
391 formatter.format( " * @param listener The listener to add.%n" );
392 formatter.format( " */%n" );
393 formatter.format( " public void addPropertyChangeListener( PropertyChangeListener listener )%n" );
394 formatter.format( " {%n" );
395 formatter.format( " if ( propertyChangeSupport == null )%n" );
396 formatter.format( " {%n" );
397 formatter.format( " propertyChangeSupport = new PropertyChangeSupport( this );%n" );
398 formatter.format( " }%n%n" );
399 formatter.format( " propertyChangeSupport.addPropertyChangeListener( listener );%n" );
400 formatter.format( " }%n%n" );
401
402 formatter.format( " /**%n" );
403 formatter.format( " * Removes a property change listener from this <code>bean</code>.%n" );
404 formatter.format( " * Delegates handling to {@link #propertyChangeSupport}%n" );
405 formatter.format( " *%n" );
406 formatter.format( " * @param listener The listener to be removed.%n" );
407 formatter.format( " */%n" );
408 formatter.format( " public void removePropertyChangeListener(%n" );
409 formatter.format( " PropertyChangeListener listener )%n" );
410 formatter.format( " {%n" );
411 formatter.format( " if ( propertyChangeSupport != null )%n" );
412 formatter.format( " {%n" );
413 formatter.format( " propertyChangeSupport.removePropertyChangeListener( listener );%n" );
414 formatter.format( " }%n" );
415 formatter.format( " }%n%n" );
416
417 formatter.format( " /**%n" );
418 formatter.format( " * Reports a bound property change to the registerd%n" );
419 formatter.format( " * <code>PropertyChangeListener</code>s.%n" );
420 formatter.format( " * Delegates handling to {@link #propertyChangeSupport}%n" );
421 formatter.format( " *%n" );
422 formatter.format( " * @param propertyName The name of the changed property.%n" );
423 formatter.format( " * @param oldValue The previous value of the property.%n" );
424 formatter.format( " * @param newValue The present value of the property.%n" );
425 formatter.format( " */%n" );
426 formatter.format( " protected void firePropertyChange( String propertyName,%n" );
427 formatter.format( " Object oldValue, Object newValue )%n" );
428 formatter.format( " {%n" );
429 formatter.format( " if ( propertyChangeSupport != null )%n" );
430 formatter.format( " {%n" );
431 formatter.format( " propertyChangeSupport.firePropertyChange(%n" );
432 formatter.format( " propertyName, oldValue, newValue );%n" );
433 formatter.format( " }%n" );
434 formatter.format( " }%n%n" );
435
436 return builder.toString();
437 }
438
439 /**
440 * Over-ridden to fire <code>PropertyChangeEvent</code>s for
441 * mutator methods.
442 *
443 * @return String The default accessor and mutator methods for
444 * the {@link #fields}.
445 */
446 @Override
447 protected String generateBeanMethods()
448 {
449 StringBuilder methods =
450 new StringBuilder( super.generateBeanMethods() );
451 String mutator = "public final void set";
452 int index = 0;
453 while ( ( index = methods.indexOf( mutator, index + 1 ) ) != -1 )
454 {
455 String propertyName = methods.substring( index + mutator.length(),
456 methods.indexOf( "(", index ) ).trim();
457 propertyName = propertyName.substring( 0, 1 ).toLowerCase() +
458 propertyName.substring( 1 );
459
460 StringBuilder builder = new StringBuilder( 32 );
461 Formatter formatter = new Formatter( builder );
462 formatter.format( " firePropertyChange( \"%s\", oldValue, %s );%n",
463 propertyName, propertyName );
464
465 int end = methods.indexOf( " }", index );
466 methods.insert( end, builder.toString() );
467 }
468
469 return methods.toString();
470 }
471
472 /**
473 * Main method.
474 */
475 public static void main( String[] args )
476 {
477 if ( args.length != 1 )
478 {
479 System.err.println(
480 "Usage: java -classpath <classpath> com.sptci.echo2.ModelGenerator <UI class>" );
481 System.exit( 2 );
482 }
483 else
484 {
485 try
486 {
487 ModelGenerator generator = new ModelGenerator( args[0] );
488 generator.generate();
489 }
490 catch ( Throwable t )
491 {
492 t.printStackTrace();
493 System.exit( 1 );
494 }
495 }
496
497 System.exit( 0 );
498 }
499 }