View Javadoc

1   /*
2    * $Source: /usr/cvsroot/melati/maven-dsd-plugin/src/main/java/org/melati/poem/prepro/FieldDef.java,v $
3    * $Revision: 1.56 $
4    *
5    * Copyright (C) 2000 William Chesters
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     William Chesters <williamc At paneris.org>
42   *     http://paneris.org/~williamc
43   *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
44   */
45  
46  package org.melati.poem.prepro;
47  
48  import java.util.Vector;
49  import java.io.StreamTokenizer;
50  import java.io.Writer;
51  import java.io.IOException;
52  
53  /**
54   * An abstract definition of a <tt>Field</tt> from which all other
55   * <tt>FieldDef</tt>s are derived.
56   *
57   */
58  public abstract class FieldDef {
59  
60    protected final TableDef table;
61  
62    protected final String name;
63  
64    protected final String capitalisedName;
65  
66    protected int displayOrder;
67  
68    String displayName;
69  
70    String description;
71  
72    /** short name eg String, User */
73    protected final String typeShortName;
74  
75    protected final String rawType;
76  
77    protected final Vector<FieldQualifier> fieldQualifiers;
78  
79    final String shortestUnambiguousClassname;
80  
81    final String tableAccessorMethod;
82  
83    protected String displayLevel = null;
84  
85    protected String searchability = null;
86  
87    private boolean sortDescending = false;
88  
89    int displayOrderPriority = -1;
90  
91    private boolean isNullable = false;
92  
93    private boolean isTroidColumn = false;
94  
95    private boolean isDeletedColumn = false;
96  
97    private boolean isEditable = true;
98  
99    private boolean isCreateable = true;
100 
101   private boolean isIndexed = false;
102 
103   private boolean isUnique = false;
104 
105   boolean isCompareOnly = false;
106 
107   private int width = -1, height = -1;
108 
109   String renderinfo = null;
110 
111   protected int lineNumber;
112 
113   /**
114    * Constructor.
115    *
116    * @param table
117    *          the {@link TableDef} that this <code>Field</code> is part of
118    * @param name
119    *          the name of this field
120    * @param type
121    *          the POEM type of this field
122    * @param rawType
123    *          the underlying java type of this field
124    * @param displayOrder
125    *          where to place this field in a list
126    * @param qualifiers
127    *          all the qualifiers to be applied to this field
128    *
129    * @throws IllegalityException
130    *           if a semantic inconsistency is detected
131    */
132   public FieldDef(int lineNo, TableDef table, String name, String type,
133       String rawType, int displayOrder, Vector<FieldQualifier> qualifiers)
134       throws IllegalityException {
135     this.lineNumber = lineNo;
136     this.table = table;
137     this.name = name;
138     this.displayOrder = displayOrder;
139     this.capitalisedName = StringUtils.capitalised(name);
140     this.typeShortName = type;
141     this.rawType = rawType;
142     this.fieldQualifiers = qualifiers;
143 
144     this.shortestUnambiguousClassname = table.tableNamingInfo.mainClassUnambiguous();
145     this.tableAccessorMethod = table.tableNamingInfo.tableAccessorMethod();
146 
147     for (int q = 0; q < qualifiers.size(); ++q) {
148       ((FieldQualifier)qualifiers.elementAt(q)).apply(this);
149     }
150 
151   }
152 
153   /** @return a name for this class */
154   public String toString() {
155     return table.name + "." + name + " (" + (isNullable ? "nullable " : "")
156         + typeShortName + ")";
157   }
158 
159   private static void readFieldQualifiers(Vector<FieldQualifier> qualifiers, StreamTokenizer tokens)
160       throws ParsingDSDException, IOException {
161     while (tokens.ttype == '(') {
162       tokens.nextToken();
163       qualifiers.addElement(FieldQualifier.from(tokens));
164       DSD.expect(tokens, ')');
165       tokens.nextToken();
166     }
167   }
168 
169   /**
170    * Creates the appropriate type of <code>FieldDef</code> from the input
171    * stream.
172    *
173    * @param table
174    *          the {@link TableDef} we are dealing with
175    * @param tokens
176    *          the <code>StreamTokenizer</code> to get tokens from
177    * @param displayOrder
178    *          the ranking of this <code>Field</code>
179    *
180    * @throws ParsingDSDException
181    *           if an unexpected token is encountered
182    * @throws IOException
183    *           if something goes wrong with the file system
184    * @throws IllegalityException
185    *           if a semantic incoherence is detected
186    * @return a new <code>FieldDef</code> of the appropriate type
187    */
188   public static FieldDef from(TableDef table, StreamTokenizer tokens,
189       int displayOrder) throws ParsingDSDException, IOException,
190       IllegalityException {
191     table.addImport("org.melati.poem.AccessPoemException", "both");
192     table.addImport("org.melati.poem.ValidationPoemException", "table");
193     table.addImport("org.melati.poem.Persistent", "table");
194 
195     table.definesColumns = true;
196     Vector<FieldQualifier> qualifiers = new Vector<FieldQualifier>();
197     readFieldQualifiers(qualifiers, tokens);
198     if (tokens.ttype != StreamTokenizer.TT_WORD)
199       throw new ParsingDSDException("<field type>", tokens);
200     String type = tokens.sval;
201     // HACK we allow "byte[]" for binary data
202     if (type.equals("byte")) {
203       if (tokens.nextToken() != '[' || tokens.nextToken() != ']')
204         throw new ParsingDSDException("[", tokens);
205       type = "byte[]";
206     }
207 
208     if (tokens.nextToken() != StreamTokenizer.TT_WORD)
209       throw new ParsingDSDException("<field name>", tokens);
210     String name = tokens.sval;
211     tokens.nextToken();
212     readFieldQualifiers(qualifiers, tokens);
213     DSD.expect(tokens, ';');
214     int lineNo = tokens.lineno();
215     if (type.equals("Integer"))
216       return new IntegerFieldDef(lineNo, table, name, displayOrder, qualifiers);
217     if (type.equals("Long"))
218       return new LongFieldDef(lineNo, table, name, displayOrder, qualifiers);
219     else if (type.equals("Double"))
220       return new DoubleFieldDef(lineNo, table, name, displayOrder, qualifiers);
221     else if (type.equals("Boolean"))
222       return new BooleanFieldDef(lineNo, table, name, displayOrder, qualifiers);
223     else if (type.equals("String"))
224       return new StringFieldDef(lineNo, table, name, displayOrder, qualifiers);
225     else if (type.equals("Password"))
226       return new PasswordFieldDef(lineNo, table, name, displayOrder, qualifiers);
227     else if (type.equals("Date"))
228       return new DateFieldDef(lineNo, table, name, displayOrder, qualifiers);
229     else if (type.equals("Timestamp"))
230       return new TimestampFieldDef(lineNo, table, name, displayOrder,
231           qualifiers);
232     else if (type.equals("Time"))
233       return new TimeFieldDef(lineNo, table, name, displayOrder,
234           qualifiers);
235     else if (type.equals("ColumnType"))
236       return new ColumnTypeFieldDef(lineNo, table, name, displayOrder,
237           qualifiers);
238     else if (type.equals("DisplayLevel"))
239       return new DisplayLevelFieldDef(lineNo, table, name, displayOrder,
240           qualifiers);
241     else if (type.equals("Searchability"))
242       return new SearchabilityFieldDef(lineNo, table, name, displayOrder,
243           qualifiers);
244     else if (type.equals("IntegrityFix"))
245       return new IntegrityFixFieldDef(lineNo, table, name, displayOrder,
246           qualifiers);
247     else if (type.equals("BigDecimal"))
248       return new BigDecimalFieldDef(lineNo, table, name, displayOrder,
249           qualifiers);
250     else if (type.equals("byte[]"))
251       return new BinaryFieldDef(lineNo, table, name, displayOrder, qualifiers);
252     else
253       return new ReferenceFieldDef(lineNo, table, name, displayOrder, type,
254           qualifiers);
255   }
256 
257   /**
258    * Write out this <code>Column</code>'s base methods.
259    *
260    * @param w
261    *          Persistent Base
262    *
263    * @throws IOException
264    *           if something goes wrong with the file system
265    */
266   public void generateBaseMethods(Writer w) throws IOException {
267     w.write("\n /**\n" + "  * Retrieves the <code>" + capitalisedName
268         + "</code> value, without locking, \n" + "  * for this <code>"
269         + table.nameFromDsd + "</code> <code>Persistent</code>.\n" + "  *\n"
270         + "  * see org.melati.poem.prepro.FieldDef"
271         + "#generateBaseMethods \n" + "  * @return the " + rawType + " " + name
272         + "\n" + "  */\n");
273     w.write("  public " + rawType + " get" + capitalisedName + "_unsafe() {\n"
274         + "    return " + name + ";\n" + "  }\n" + "\n");
275     w.write("\n /**\n" + "  * Sets the <code>" + capitalisedName
276         + "</code> value directly, without checking, \n" + "  * for this "
277         + table.nameFromDsd + " <code>Persistent</code>.\n" + "  * \n"
278         + "  * see org.melati.poem.prepro.FieldDef"
279         + "#generateBaseMethods \n"
280         + "  * @param cooked  the pre-validated value to set\n" + "  */\n");
281     w.write("  public void set" + capitalisedName + "_unsafe(" + rawType
282         + " cooked) {\n" + "    " + name + " = cooked;\n" + "  }\n");
283   }
284 
285   /**
286    * Write out this <code>Column</code>'s field creators.
287    *
288    * @param w
289    *          Persistent Base
290    * @throws IOException
291    *           if something goes wrong with the file system
292    */
293   public void generateFieldCreator(Writer w) throws IOException {
294     w.write("\n /**\n" 
295         + "  * Retrieves the <code>" + capitalisedName + "</code> value as a <code>Field</code>\n" 
296         + "  * from this <code>" + table.nameFromDsd + "</code> <code>Persistent</code>.\n" 
297         + "  * \n"
298         + "  * see org.melati.poem.prepro.FieldDef#generateFieldCreator \n" 
299         + "  * @throws AccessPoemException \n"
300         + "  *         if the current <code>AccessToken</code> \n"
301         + "  *         does not confer write access rights\n"
302         + "  * @return the " + rawType + " " + name + "\n" 
303         + "  */\n");
304     w.write("  public Field<" + rawType + "> get" + capitalisedName + "Field() throws AccessPoemException {\n" 
305         + "    Column<"+rawType+"> c = _"+ tableAccessorMethod + "()." + "get" + capitalisedName + "Column();\n"
306         + "    return new Field<"+rawType+">(("+rawType+")c.getRaw(this), c);\n" 
307         + "  }\n");
308   }
309 
310   /**
311    * Write out this <code>Field</code>'s java declaration string.
312    *
313    * @param w
314    *          PersistentBase
315    * @throws IOException
316    *           if something goes wrong with the file system
317    */
318   public abstract void generateJavaDeclaration(Writer w) throws IOException;
319 
320   /**
321    * Write out this <code>Column</code>'s java declaration string.
322    *
323    * @param w
324    *          TableBase
325    * @throws IOException
326    *           if something goes wrong with the file system
327    */
328   public void generateColDecl(Writer w) throws IOException {
329     // FIXME This should be different for ref types
330     w.write("Column<"+rawType+"> col_" + name);
331   }
332 
333   /**
334    * Write out this <code>Column</code>'s accessors.
335    *
336    * @param w
337    *          TableBase
338    * @throws IOException
339    *           if something goes wrong with the file system
340    */
341   public void generateColAccessor(Writer w) throws IOException {
342     w.write("\n /**\n" 
343         + "  * Retrieves the <code>" + capitalisedName + "</code> <code>Column</code> for this \n" 
344         + "  * <code>"+ table.nameFromDsd + "</code> <code>Table</code>.\n" + "  * \n"
345         + "  * see org.melati.poem.prepro.FieldDef#generateColAccessor \n" 
346         + "  * @return the " + name + " <code>Column</code>\n" 
347         + "  */\n");
348     w.write("  public final Column<"+rawType+"> get" + capitalisedName + "Column() {\n"
349         + "    return col_" + name + ";\n" + "  }\n");
350   }
351 
352   /**
353    * Write out this <code>Column</code>'s field accessors as part of the
354    * anonymous definition of the <code>Column</code>.
355    *
356    * @param w
357    *          TableBase
358    * @throws IOException
359    *           if something goes wrong with the file system
360    */
361   protected void generateColRawAccessors(Writer w) throws IOException {
362     w.write("          public Object getRaw_unsafe(Persistent g)\n"
363         + "              throws AccessPoemException {\n"
364         + "            return ((" + shortestUnambiguousClassname + ")g)." + "get" + capitalisedName
365         + "_unsafe();\n" + "          }\n" + "\n");
366 
367     w.write("          public void setRaw_unsafe(Persistent g, Object raw)\n"
368         + "              throws AccessPoemException {\n" + "            (("
369         + shortestUnambiguousClassname + ")g).set" + capitalisedName + "_unsafe((" + rawType + ")raw);\n"
370         + "          }\n");
371   }
372 
373   /**
374    * Write out this <code>Column</code>'s definition using an anonymous
375    * class.
376    *
377    * @param w
378    *          TableBase
379    * @throws IOException
380    *           if something goes wrong with the file system
381    */
382   public void generateColDefinition(Writer w) throws IOException {
383     w
384         .write("    defineColumn(col_"
385             + name
386             + " =\n"
387             + "        new Column<"+rawType +">(this, \""
388             + name
389             + "\",\n"
390             + "                   "
391             + poemTypeJava()
392             + ",\n"
393             + "                   DefinitionSource.dsd) { \n"
394             + "          public Object getCooked(Persistent g)\n"
395             + "              throws AccessPoemException, PoemException {\n"
396             + "            return (("
397             + shortestUnambiguousClassname
398             + ")g).get"
399             + capitalisedName
400             + "();\n"
401             + "          }\n"
402             + "\n"
403             + "          public void setCooked(Persistent g, Object cooked)\n"
404             + "              throws AccessPoemException, ValidationPoemException {\n"
405             + "            ((" + shortestUnambiguousClassname + ")g).set" + capitalisedName + "((" + typeShortName
406             + ")cooked);\n" + "          }\n" + "\n"
407             + "          public Field<"+rawType+"> asField(Persistent g) {\n"
408             + "            return ((" + shortestUnambiguousClassname + ")g).get" + capitalisedName
409             + "Field();\n" + "          }\n" + "\n");
410 
411     if (isTroidColumn || !isEditable)
412       w.write("          public boolean defaultUserEditable() {\n"
413           + "            return false;\n" + "          }\n" + "\n");
414 
415     if (isTroidColumn || !isCreateable)
416       w.write("          public boolean defaultUserCreateable() {\n"
417           + "            return false;\n" + "          }\n" + "\n");
418 
419     if (displayLevel != null)
420       w.write("          public DisplayLevel defaultDisplayLevel() {\n"
421           + "            return DisplayLevel." + displayLevel + ";\n"
422           + "          }\n" + "\n");
423 
424     if (searchability != null)
425       w.write("          public Searchability defaultSearchability() {\n"
426           + "            return Searchability." + searchability + ";\n"
427           + "          }\n" + "\n");
428 
429     if (displayOrderPriority != -1)
430       w.write("          public Integer defaultDisplayOrderPriority() {\n"
431           + "            return new Integer(" + displayOrderPriority + ");\n"
432           + "          }\n" + "\n");
433 
434     if (sortDescending)
435       w.write("          public boolean defaultSortDescending() {\n"
436           + "            return true;\n" + "          }\n" + "\n");
437 
438     if (displayName != null)
439       w.write("          public String defaultDisplayName() {\n"
440           + "            return " + StringUtils.quoted(displayName, '"')
441           + ";\n" + "          }\n" + "\n");
442 
443     w
444         .write("          public int defaultDisplayOrder() {\n"
445             + "            return " + displayOrder + ";\n" + "          }\n"
446             + "\n");
447 
448     if (description != null)
449       w.write("          public String defaultDescription() {\n"
450           + "            return " + StringUtils.quoted(description, '"')
451           + ";\n" + "          }\n" + "\n");
452 
453     if (isIndexed)
454       w.write("          public boolean defaultIndexed() {\n"
455           + "            return true;\n" + "          }\n" + "\n");
456 
457     if (isUnique)
458       w.write("          public boolean defaultUnique() {\n"
459           + "            return true;\n" + "          }\n" + "\n");
460 
461     if (width != -1)
462       w.write("          public int defaultWidth() {\n"
463           + "            return " + width + ";\n" + "          }\n" + "\n");
464 
465     if (height != -1)
466       w.write("          public int defaultHeight() {\n"
467           + "            return " + height + ";\n" + "          }\n" + "\n");
468 
469     if (renderinfo != null)
470       w.write("          public String defaultRenderinfo() {\n"
471           + "            return " + StringUtils.quoted(renderinfo, '"') + ";\n"
472           + "          }\n" + "\n");
473 
474     generateColRawAccessors(w);
475 
476     w.write("        });\n");
477   }
478 
479   /** @return the Java string for this <code>PoemType</code>. */
480   public abstract String poemTypeJava();
481 
482   /**
483    * @return whether this column is a deleted marker
484    */
485   public boolean isDeletedColumn() {
486     return isDeletedColumn;
487   }
488 
489   /**
490    * Set whether this field represents a deleted marker.
491    *
492    * @param isDeletedColumn boolean
493    */
494   public void setDeletedColumn(boolean isDeletedColumn) {
495     if (this.isDeletedColumn)
496       throw new IllegalityException(lineNumber,
497           "Deleted qualifier already set true.");
498     this.isDeletedColumn = isDeletedColumn;
499   }
500 
501   /**
502    * @return whether this field represents a troid column.
503    */
504   public boolean isTroidColumn() {
505     return isTroidColumn;
506   }
507 
508   /**
509    * Set the isTroidColumn property.
510    *
511    * @param isTroidColumn boolean
512    */
513   public void setTroidColumn(boolean isTroidColumn) {
514     if (this.isTroidColumn)
515       throw new IllegalityException(lineNumber,
516           "Troid qualifier  already set true.");
517     this.isTroidColumn = isTroidColumn;
518   }
519 
520   /**
521    * @return whether this column is nullable.
522    */
523   public boolean isNullable() {
524     return isNullable;
525   }
526 
527   /**
528    * Set the nullable property.
529    *
530    * @param isNullable boolean
531    */
532   public void setNullable(boolean isNullable) {
533     if (this.isNullable)
534       throw new IllegalityException(lineNumber,
535           "Nullable qualifier  already set true.");
536     this.isNullable = isNullable;
537   }
538 
539   /**
540    * @return whether this field is editable
541    */
542   public boolean isEditable() {
543     return isEditable;
544   }
545 
546   /**
547    * Set the isEditable property.
548    *
549    * @param isEditable boolean
550    */
551   public void setEditable(boolean isEditable) {
552     if (!this.isEditable)
553       throw new IllegalityException(lineNumber,
554           "Editable qualifier  already set true.");
555     this.isEditable = isEditable;
556   }
557 
558   /**
559    * @return whether this column shoudl be sorted in descending order
560    */
561   public boolean isSortDescending() {
562     return sortDescending;
563   }
564 
565   /**
566    * Set the sortDescending property.
567    *
568    * @param sortDescending
569    */
570   public void setSortDescending(boolean sortDescending) {
571     if (this.sortDescending)
572       throw new IllegalityException(lineNumber,
573           "Sort descending qualifier  already set true.");
574     this.sortDescending = sortDescending;
575   }
576 
577   /**
578    * @return whether this column is user createable
579    */
580   public boolean isCreateable() {
581     return isCreateable;
582   }
583 
584   /**
585    * Set the isCreatable property.
586    *
587    * @param isCreateable boolean
588    */
589   public void setCreateable(boolean isCreateable) {
590     if (!this.isCreateable)
591       throw new IllegalityException(lineNumber,
592           "Creatable qualifier  already set true.");
593     this.isCreateable = isCreateable;
594   }
595 
596   /**
597    * @return whether this column is indexed.
598    */
599   public boolean isIndexed() {
600     return isIndexed;
601   }
602 
603   /**
604    * Set the isIndexed property.
605    * @param isIndexed boolean
606    */
607   public void setIndexed(boolean isIndexed) {
608     if (this.isIndexed)
609       throw new IllegalityException(lineNumber,
610           "Indexed qualifier  already set true.");
611     this.isIndexed = isIndexed;
612   }
613 
614   /**
615    * @return whether this column is unique
616    */
617   public boolean isUnique() {
618     return isUnique;
619   }
620 
621   /**
622    * Set the isUnique property.
623    *
624    * @param isUnique boolean
625    */
626   public void setUnique(boolean isUnique) {
627     if (this.isUnique)
628       throw new IllegalityException(lineNumber,
629           "Unique qualifier  already set true.");
630     this.isUnique = isUnique;
631   }
632 
633   /**
634    * @return the width
635    */
636   public int getWidth() {
637     return width;
638   }
639 
640   /**
641    * Set the width property.
642    *
643    * @param width the width to set
644    */
645   public void setWidth(int width) {
646     if (this.width != -1)
647       throw new IllegalityException(lineNumber, "Size already set to "
648           + this.width + " cannot overwrite with " + width);
649     this.width = width;
650   }
651 
652   /**
653    * @return the height
654    */
655   public int getHeight() {
656     return height;
657   }
658 
659   /**
660    * Set the height property.
661    *
662    * @param height the height to set
663    */
664   public void setHeight(int height) {
665     if (this.height != -1)
666       throw new IllegalityException(lineNumber, "Height already set to "
667           + this.width + " cannot overwrite with " + width);
668     this.height = height;
669   }
670 }