001    package com.sptci;
002    
003    import java.lang.reflect.Field;
004    
005    import java.util.Formatter;
006    import java.util.Map;
007    import java.util.HashSet;
008    import java.util.TreeMap;
009    
010    /**
011     * An abstract code generator.  Generates the code for common methods
012     * that are implemented in source code.
013     *
014     * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
015     * @author Rakesh Vidyadharan 2006-02-07
016     * @version $Id: CodeGenerator.java,v 1.2 2006/02/09 16:09:13 rakesh Exp $
017     */
018    public abstract class CodeGenerator
019    {
020      /**
021       * The name of the class that is being generated.
022       */
023      protected String name;
024    
025      /**
026       * A <code>Map</code> of <code>fields</code> for the generated class.
027       * The name of the field is stored as the <code>key</code> and the
028       * type of the field as the <code>value</code>.
029       */
030      protected Map<String, String> fields;
031    
032      /**
033       * A <code>HashSet</code> containing the primitive types defined
034       * for Java.
035       */
036      protected HashSet<String> primitives;
037    
038      /**
039       * Default constructor.  Initialises code generator containers.
040       */
041      public CodeGenerator()
042      {
043        fields = new TreeMap<String, String>();
044        primitives = new HashSet<String>();
045        primitives.add( "boolean" );
046        primitives.add( "byte" );
047        primitives.add( "char" );
048        primitives.add( "double" );
049        primitives.add( "float" );
050        primitives.add( "int" );
051        primitives.add( "long" );
052        primitives.add( "short" );
053      }
054    
055      /**
056       * The abstract method that will generate the full source code.
057       *
058       * @throws Exception If errors are encountered while generating
059       *   the source file.
060       */
061      public abstract void generate() throws Exception;
062    
063      /**
064       * Generate the default fields for the class.  Sub-classes must
065       * over-ride this method, while usually using the results of this
066       * method to enhance their output.
067       *
068       * @return String The default fields to be added to each source file.
069       */
070      protected String generateFields()
071      {
072        StringBuilder builder = new StringBuilder( 256 );
073        Formatter formatter = new Formatter( builder );
074    
075        formatter.format( "  /**%n" );
076        formatter.format( "   * The default value to use as the base for hashCode.%n" );
077        formatter.format( "   */%n" );
078        formatter.format( "  protected static final int HASH = 7;%n%n" );
079    
080        formatter.format( "  /**%n" );
081        formatter.format( "   * The field used to store pre-computed hashCode.%n" );
082        formatter.format( "   */%n" );
083        formatter.format( "  protected int hash = HASH;%n%n" );
084    
085        return builder.toString();
086      }
087    
088      /**
089       * Generate the <code>toString</code> method for the JavaBean.
090       * Generate an XML representation of the class and its fields.
091       *
092       * @return String The default <code>toString</code> method
093       *   implementation.
094       */
095      protected String generateToStringMethod()
096      {
097        StringBuilder builder = new StringBuilder( 2048 );
098        Formatter formatter = new Formatter( builder );
099    
100        formatter.format( "  /**%n" );
101        formatter.format( "   * Return a String representation of the fields of this class.%n" );
102        formatter.format( "   * The String representation is returned as an XML document.%n" );
103        formatter.format( "   *%n" );
104        formatter.format( "   * @return String An XML representation of the class fields.%n" );
105        formatter.format( "   */%n" );
106        formatter.format( "  @Override%n" );
107        formatter.format( "  public String toString()%n" );
108        formatter.format( "  {%n" );
109    
110        formatter.format( "    StringBuilder builder = new StringBuilder( 4096 );%n" );
111        formatter.format( "    Formatter formatter = new Formatter( builder );%n" );
112    
113        formatter.format( "    formatter.format( \"<%s package='%%s'>%%n\", getClass().getPackage().getName() );%n%n", name );
114    
115        for ( Map.Entry<String, String> entry : fields.entrySet() )
116        {
117          formatter.format( "    formatter.format( \"<%s type='%s'>%%n\" );%n",
118              entry.getKey(), 
119              entry.getValue().replaceAll( "<", "&lt;" ).replaceAll( ">", "&gt;" ) );
120    
121          if ( entry.getValue().startsWith( "Map<" ) )
122          {
123            formatter.format( 
124                "    for ( Map.Entry<%s> entry : %s.entrySet() )%n",
125                entry.getValue().substring( 
126                  entry.getValue().indexOf( "<" ) + 1,
127                  entry.getValue().indexOf( ">" ) ), entry.getKey() );
128            formatter.format( "    {%n" );
129            formatter.format( "      formatter.format( \"<key>%%n%%s%%n</key>%%n\", entry.getKey() );%n" );
130            formatter.format( "      formatter.format( \"<value>%%n%%s%%n</value>%%n\", entry.getValue() );%n" );
131            formatter.format( "    }%n" );
132          }
133          else if ( entry.getValue().startsWith( "List<" ) )
134          {
135            formatter.format( 
136                "    for ( %s entry : %s )%n",
137                entry.getValue().substring( 
138                  entry.getValue().indexOf( "<" ) + 1,
139                  entry.getValue().indexOf( ">" ) ), entry.getKey() );
140            formatter.format( "    {%n" );
141            formatter.format( "      formatter.format( \"<entry>%%n%%s%%n</entry>%%n\", entry );%n" );
142            formatter.format( "    }%n" );
143          }
144          else
145          {
146            formatter.format( "    formatter.format( \"%%s%%n\", %s );%n",
147                entry.getKey() );
148          }
149    
150          formatter.format( "    formatter.format( \"</%s>%%n\" );%n%n",
151              entry.getKey() );
152        }
153    
154        formatter.format( "    formatter.format( \"</%s>%%n\" );%n", name );
155    
156        formatter.format( "    return builder.toString();%n" );
157        formatter.format( "  }%n" );
158    
159        return builder.toString();
160      }
161    
162      /**
163       * Generate the <code>equals</code> method for the JavaBean.
164       *
165       * @return String The default <code>equals</code> method 
166       *   implementation.
167       */
168      protected String generateEqualsMethod()
169      {
170        StringBuilder builder = new StringBuilder( 1024 );
171        Formatter formatter = new Formatter( builder );
172    
173        formatter.format( "  /**%n" );
174        formatter.format( "   * Compare the specified object with this object for equality.%n" );
175        formatter.format( "   * Returns <code>true</code> if the specified object is of the%n" );
176        formatter.format( "   * same type as this  object, and the values of the fields%n" );
177        formatter.format( "   * are equal.%n" );
178        formatter.format( "   *%n" );
179        formatter.format( "   * @param object The object that is to compared with this object.%n" );
180        formatter.format( "   * @return boolean Returns <code>true</code> if the values of%n" );
181        formatter.format( "   *   the class fields are the same.%n" );
182        formatter.format( "   */%n" );
183        formatter.format( "  @Override%n" );
184        formatter.format( "  public boolean equals( Object object )%n" );
185        formatter.format( "  {%n" );
186        formatter.format( "    if ( this == object ) return true;%n%n" );
187        formatter.format( "    boolean value = false;%n" );
188        formatter.format( "    if ( ( object != null ) &&%n" );
189        formatter.format( "        ( getClass() == object.getClass() ) )%n" );
190        formatter.format( "    {%n" );
191        formatter.format( "      %s obj = (%s) object;%n%n", name, name );
192        formatter.format( "      value = ( hashCode() == obj.hashCode() );%n" );
193    
194        int count = 0;
195        for ( Map.Entry<String, String> entry : fields.entrySet() )
196        {
197          formatter.format( "      value = value && " );
198    
199          if ( primitives.contains( entry.getValue() ) )
200          {
201            formatter.format( " ( %s == obj.%s );%n", 
202                entry.getKey(), entry.getKey() );
203          }
204          else
205          {
206            formatter.format( " ( ( %s == obj.%s ) ||%n",
207                entry.getKey(), entry.getKey() );
208            formatter.format( "          ( %s != null &&%n", entry.getKey() );
209            formatter.format( "            %s.equals( obj.%s ) ) );%n",
210              entry.getKey(), entry.getKey() );
211          }
212    
213          ++count;
214        }
215    
216        formatter.format( "    }%n%n" );
217        formatter.format( "    return value;%n" );
218        formatter.format( "  }%n" );
219    
220        return builder.toString();
221      }
222    
223      /**
224       * Generate the <code>equals</code> method for the JavaBean.
225       *
226       * @return String The default <code>hashCode</code> method 
227       *   implementation.
228       */
229      protected String generateHashCodeMethod()
230      {
231        StringBuilder builder = new StringBuilder( 1024 );
232        Formatter formatter = new Formatter( builder );
233    
234        formatter.format( "  /**%n" );
235        formatter.format( "   * Return a hash code for this object.  Returns a hash code%n" );
236        formatter.format( "   * computed from the class fields.%n" );
237        formatter.format( "   *%n" );
238        formatter.format( "   * @return int The hashCode value.%n" );
239        formatter.format( "   */%n" );
240        formatter.format( "  @Override%n" );
241        formatter.format( "  public int hashCode()%n" );
242        formatter.format( "  {%n" );
243        formatter.format( "    if ( hash == HASH )%n" );
244        formatter.format( "    {%n" );
245        for ( Map.Entry<String, String> entry : fields.entrySet() )
246        {
247          if ( primitives.contains( entry.getValue() ) )
248          {
249            if ( entry.getValue().equals( "boolean" ) )
250            {
251              formatter.format( "      hash = ( 31 * hash ) +%n" );
252              formatter.format( "        ( ( %s ) ? 1 : 0;%n",
253                  entry.getKey() );
254            }
255            else
256            {
257              formatter.format( 
258                  "      hash = ( 31 * hash ) + ( (int) %s );%n", 
259                  entry.getKey() );
260            }
261          }
262          else
263          {
264            formatter.format( "      hash = ( 31 * hash ) +%n" );
265            formatter.format( "        ( ( %s == null ) ? 0 : %s.hashCode() );%n",
266                entry.getKey(), entry.getKey() );
267          }
268        }
269        formatter.format( "    }%n" );
270    
271        formatter.format( "%n    return hash;%n" );
272        formatter.format( "  }%n%n" );
273    
274        return builder.toString();
275      }
276    
277      /**
278       * Generate the <code>compareTo</code> method for the JavaBean.
279       *
280       * @return String The default <code>compareTo</code> method
281       *   implementation.
282       */
283      protected String generateCompareToMethod()
284      {
285        StringBuilder builder = new StringBuilder( 1024 );
286        Formatter formatter = new Formatter( builder );
287    
288        formatter.format( "  /**%n" );
289        formatter.format( "   * Implementation of the <code>Comparable</code> interface.%n" );
290        formatter.format( "   * Compares this object with the specified object for order. Returns%n" );
291        formatter.format( "   * a negative integer, zero, or a positive integer as this object is%n" );
292        formatter.format( "   * less than, equal to, or greater than the specified object.%n" );
293        formatter.format( "   * The comparison is done by comparing the {@link #hashCode()} values%n" );
294        formatter.format( "   * of the objects.%n" );
295        formatter.format( "   *%n" );
296        formatter.format( "   * @param object - The object with which this class is to%n" );
297        formatter.format( "   *   be compared.  No class type checking is done.%n" );
298        formatter.format( "   * @return int - A negative integer, zero, or a positive integer as%n" );
299        formatter.format( "   *   this object is less than, equal to, or greater than the%n" );
300        formatter.format( "   *   specified object.%n" );
301        formatter.format( "   */%n" );
302        formatter.format( "  public int compareTo( %s object )%n", name );
303        formatter.format( "  {%n" );
304        formatter.format( "    return ( this.hashCode() - object.hashCode() );%n" );
305        formatter.format( "  }%n%n" );
306    
307        return builder.toString();
308      }
309    
310      /**
311       * Generate the clone method for the JavaBean.
312       *
313       * @return String The default <code>clone</code> method implementation.
314       */
315      protected String generateCloneMethod()
316      {
317        StringBuilder builder = new StringBuilder( 1024 );
318        Formatter formatter = new Formatter( builder );
319    
320        formatter.format( "  /**%n" );
321        formatter.format( "   * Creates and returns a copy of this object.  Implementation of%n" );
322        formatter.format( "   * the <code>Cloneable</code> interface.  No special actions are%n" );
323        formatter.format( "   * performed.  This method simply allows public access to the%n" );
324        formatter.format( "   * <code>Object.clone</code> method.%n" );
325        formatter.format( "   *%n" );
326        formatter.format( "   * @return Object A clone of this instance.%n" );
327        formatter.format( "   * @throws CloneNotSupportedException If the super-class implementation%n" );
328        formatter.format( "   *   throws an error.%n" );
329        formatter.format( "   */%n" );
330        formatter.format( "  @Override%n" );
331        formatter.format( "  public Object clone() throws CloneNotSupportedException%n" );
332        formatter.format( "  {%n" );
333        formatter.format( "    return super.clone();%n" );
334        formatter.format( "  }%n%n" );
335    
336        return builder.toString();
337      }
338    
339      /**
340       * Generate the accessor and mutator methods for the JavaBean.
341       *
342       * @return String The default accessor and mutator methods for
343       *   the {@link #fields}.
344       */
345      protected String generateBeanMethods()
346      {
347        StringBuilder builder = new StringBuilder( 4096 );
348        Formatter formatter = new Formatter( builder );
349        for ( Map.Entry<String, String> entry : fields.entrySet() )
350        {
351          formatter.format( "%n  /**%n" );
352          formatter.format( 
353              "   * Returns the value of {@link #%s}.%n",
354              entry.getKey() );
355          formatter.format( "   *%n" );
356          formatter.format( "   * @return %s The value of %s.%n",
357              entry.getValue(), entry.getKey() );
358          formatter.format( "   */%n" );
359          formatter.format( "  public final %s get%s%s()%n",
360              entry.getValue(), 
361              entry.getKey().substring( 0, 1 ).toUpperCase(),
362              entry.getKey().substring( 1 ) );
363          formatter.format( "  {%n" );
364          formatter.format( "    return %s;%n", entry.getKey() );
365          formatter.format( "  }%n%n" );
366    
367          formatter.format( "  /**%n" );
368          formatter.format( 
369              "   * Sets the value of {@link #%s}.  Resets {@link #hash}.%n",
370              entry.getKey() );
371          formatter.format( "   *%n" );
372          formatter.format( "   * @param %s The value to set.%n",
373              entry.getKey() );
374          formatter.format( "   */%n" );
375          formatter.format( "  public final void set%s%s( %s %s )%n",
376              entry.getKey().substring( 0, 1 ).toUpperCase(),
377              entry.getKey().substring( 1 ),
378              entry.getValue(), entry.getKey() );
379          formatter.format( "  {%n" );
380          formatter.format( "    %s oldValue = this.%s;%n",
381              entry.getValue(), entry.getKey() );
382          formatter.format( "    this.%s = %s;%n", 
383              entry.getKey(), entry.getKey() );
384          formatter.format( "    hash = HASH;%n" );
385          formatter.format( "  }%n" );
386        }
387    
388        return builder.toString();
389      }
390    }