View Javadoc
1   /*
2    * $Source$
3    * $Revision$
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 rootTableAccessorMethod;
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 lineNo       the line number in the DSD file
117    * @param table        the {@link TableDef} that this <code>Field</code> is part of
118    * @param name         the name of this field
119    * @param type         the POEM type of this field
120    * @param rawType      the underlying java type of this field
121    * @param displayOrder where to place this field in a list
122    * @param qualifiers   all the qualifiers to be applied to this field
123    * @throws IllegalityException if a semantic inconsistency is detected
124    */
125   public FieldDef(int lineNo, TableDef table, String name, String type,
126       String rawType, int displayOrder, Vector<FieldQualifier> qualifiers)
127       throws IllegalityException {
128     this.lineNumber = lineNo;
129     this.table = table;
130     this.name = name;
131     this.displayOrder = displayOrder;
132     this.capitalisedName = StringUtils.capitalised(name);
133     this.typeShortName = type;
134     this.rawType = rawType;
135     this.fieldQualifiers = qualifiers;
136 
137     this.shortestUnambiguousClassname = table.tableNamingInfo.mainClassUnambiguous();
138     this.rootTableAccessorMethod = table.tableNamingInfo.rootTableAccessorName();
139 
140     for (int q = 0; q < qualifiers.size(); ++q) {
141       ((FieldQualifier)qualifiers.elementAt(q)).apply(this);
142     }
143 
144   }
145 
146   /** @return a name for this class */
147   public String toString() {
148     return table.name + "." + name + " (" + (isNullable ? "nullable " : "")
149         + typeShortName + ")";
150   }
151 
152   private static void readFieldQualifiers(Vector<FieldQualifier> qualifiers, StreamTokenizer tokens)
153       throws ParsingDSDException, IOException {
154     while (tokens.ttype == '(') {
155       tokens.nextToken();
156       qualifiers.addElement(FieldQualifier.from(tokens));
157       DSD.expect(tokens, ')');
158       tokens.nextToken();
159     }
160   }
161 
162   /**
163    * Creates the appropriate type of <code>FieldDef</code> from the input
164    * stream.
165    *
166    * @param table
167    *          the {@link TableDef} we are dealing with
168    * @param tokens
169    *          the <code>StreamTokenizer</code> to get tokens from
170    * @param displayOrder
171    *          the ranking of this <code>Field</code>
172    *
173    * @throws ParsingDSDException
174    *           if an unexpected token is encountered
175    * @throws IOException
176    *           if something goes wrong with the file system
177    * @throws IllegalityException
178    *           if a semantic incoherence is detected
179    * @return a new <code>FieldDef</code> of the appropriate type
180    */
181   public static FieldDef from(TableDef table, StreamTokenizer tokens,
182       int displayOrder) throws ParsingDSDException, IOException,
183       IllegalityException {
184     table.addImport("org.melati.poem.AccessPoemException", "both");
185     table.addImport("org.melati.poem.ValidationPoemException", "table");
186     table.addImport("org.melati.poem.Persistent", "table");
187 
188     table.definesColumns = true;
189     Vector<FieldQualifier> qualifiers = new Vector<FieldQualifier>();
190     readFieldQualifiers(qualifiers, tokens);
191     if (tokens.ttype != StreamTokenizer.TT_WORD)
192       throw new ParsingDSDException("<field type>", tokens);
193     String type = tokens.sval;
194     // HACK we allow "byte[]" for binary data
195     if (type.equals("byte")) {
196       if (tokens.nextToken() != '[' || tokens.nextToken() != ']')
197         throw new ParsingDSDException("[", tokens);
198       type = "byte[]";
199     }
200 
201     if (tokens.nextToken() != StreamTokenizer.TT_WORD)
202       throw new ParsingDSDException("<field name>", tokens);
203     String name = tokens.sval;
204     String targetKeyName = null;
205     boolean stringKeyReference = false;
206     // handle Type StringKeyReference on targetKeyName localKeyName
207     if (name.equals("StringKeyReference")) {
208       stringKeyReference = true;
209       if (tokens.nextToken() != StreamTokenizer.TT_WORD)
210         throw new ParsingDSDException("keyword 'on'", tokens);
211       if (!tokens.sval.equals("on"))
212         throw new ParsingDSDException("keyword 'on'", tokens);
213       if (tokens.nextToken() != StreamTokenizer.TT_WORD)
214         throw new ParsingDSDException("<target key field name>", tokens);
215       targetKeyName = tokens.sval;
216       if (tokens.nextToken() != StreamTokenizer.TT_WORD)
217         throw new ParsingDSDException("<target key field name>", tokens);
218       name = tokens.sval;
219     }
220     tokens.nextToken();
221     readFieldQualifiers(qualifiers, tokens);
222     DSD.expect(tokens, ';');
223     int lineNo = tokens.lineno();
224     if (type.equals("Integer"))
225       return new IntegerFieldDef(lineNo, table, name, displayOrder, qualifiers);
226     if (type.equals("Long"))
227       return new LongFieldDef(lineNo, table, name, displayOrder, qualifiers);
228     else if (type.equals("Double"))
229       return new DoubleFieldDef(lineNo, table, name, displayOrder, qualifiers);
230     else if (type.equals("Boolean"))
231       return new BooleanFieldDef(lineNo, table, name, displayOrder, qualifiers);
232     else if (type.equals("String"))
233       return new StringFieldDef(lineNo, table, name, displayOrder, qualifiers);
234     else if (type.equals("Password"))
235       return new PasswordFieldDef(lineNo, table, name, displayOrder, qualifiers);
236     else if (type.equals("Date"))
237       return new DateFieldDef(lineNo, table, name, displayOrder, qualifiers);
238     else if (type.equals("Timestamp"))
239       return new TimestampFieldDef(lineNo, table, name, displayOrder,
240           qualifiers);
241     else if (type.equals("Time"))
242       return new TimeFieldDef(lineNo, table, name, displayOrder,
243           qualifiers);
244     else if (type.equals("ColumnType"))
245       return new ColumnTypeFieldDef(lineNo, table, name, displayOrder,
246           qualifiers);
247     else if (type.equals("DisplayLevel"))
248       return new DisplayLevelFieldDef(lineNo, table, name, displayOrder,
249           qualifiers);
250     else if (type.equals("Searchability"))
251       return new SearchabilityFieldDef(lineNo, table, name, displayOrder,
252           qualifiers);
253     else if (type.equals("IntegrityFix"))
254       return new IntegrityFixFieldDef(lineNo, table, name, displayOrder,
255           qualifiers);
256     else if (type.equals("BigDecimal"))
257       return new BigDecimalFieldDef(lineNo, table, name, displayOrder,
258           qualifiers);
259     else if (type.equals("byte[]"))
260       return new BinaryFieldDef(lineNo, table, name, displayOrder, qualifiers);
261     else {
262       if (stringKeyReference)    
263         return new StringKeyReferenceFieldDef(lineNo, table, name, targetKeyName, displayOrder, type,
264             qualifiers);
265       else
266         return new ReferenceFieldDef(lineNo, table, name, displayOrder, type,
267             qualifiers);
268     }
269   }
270 
271   /**
272    * Write out this <code>Column</code>'s base methods.
273    *
274    * @param w
275    *          Persistent Base
276    *
277    * @throws IOException
278    *           if something goes wrong with the file system
279    */
280   public void generateBaseMethods(Writer w) throws IOException {
281     w.write("\n /**\n" 
282         + "  * Retrieves the <code>" + capitalisedName + "</code> value, without locking, \n" 
283         + "  * for this <code>" + table.nameFromDsd + "</code> <code>Persistent</code>.\n" 
284         + "  *\n"
285         + "  * see org.melati.poem.prepro.FieldDef#generateBaseMethods \n" 
286         + "  * @return the " + rawType + " " + name + "\n" 
287         + "  */\n");
288     w.write("  public " + rawType + " get" + capitalisedName + "_unsafe() {\n"
289         + "    return " + name + ";\n" + "  }\n" 
290         + "\n");
291     w.write("\n /**\n" + "  * Sets the <code>" + capitalisedName + "</code> value directly, without checking, \n" + 
292         "  * for this " + table.nameFromDsd + " <code>Persistent</code>.\n" 
293         + "  * \n"
294         + "  * see org.melati.poem.prepro.FieldDef#generateBaseMethods \n"
295         + "  * @param cooked  the pre-validated value to set\n" + "  */\n");
296     w.write("  public void set" + capitalisedName + "_unsafe(" + rawType + " cooked) {\n" 
297         + "    " + name + " = cooked;\n" 
298         + "  }\n");
299   }
300 
301   /**
302    * Write out this <code>Column</code>'s field creators.
303    *
304    * @param w
305    *          Persistent Base
306    * @throws IOException
307    *           if something goes wrong with the file system
308    */
309   public void generateFieldCreator(Writer w) throws IOException {
310     w.write("\n /**\n" 
311         + "  * Retrieves the <code>" + capitalisedName + "</code> value as a <code>Field</code>\n" 
312         + "  * from this <code>" + table.nameFromDsd + "</code> <code>Persistent</code>.\n" 
313         + "  * \n"
314         + "  * see org.melati.poem.prepro.FieldDef#generateFieldCreator \n" 
315         + "  * @throws AccessPoemException \n"
316         + "  *         if the current <code>AccessToken</code> \n"
317         + "  *         does not confer write access rights\n"
318         + "  * @return the " + rawType + " " + name + "\n" 
319         + "  */\n");
320     w.write(
321           "  public Field<" + rawType + "> get" + capitalisedName + "Field() throws AccessPoemException {\n" 
322         + "    Column<"+rawType+"> c = _"+ rootTableAccessorMethod + "()." + "get" + capitalisedName + "Column();\n"
323         + "    return new Field<"+rawType+">(("+rawType+")c.getRaw(this), c);\n" 
324         + "  }\n");
325   }
326 
327   /**
328    * Write out this <code>Field</code>'s java declaration string.
329    *
330    * @param w
331    *          PersistentBase
332    * @throws IOException
333    *           if something goes wrong with the file system
334    */
335   public abstract void generateJavaDeclaration(Writer w) throws IOException;
336 
337   /**
338    * Write out this <code>Column</code>'s java declaration string.
339    *
340    * @param w
341    *          TableBase
342    * @throws IOException
343    *           if something goes wrong with the file system
344    */
345   public void generateColDecl(Writer w) throws IOException {
346     // FIXME This should be different for ref types
347     w.write("Column<"+rawType+"> col_" + name);
348   }
349 
350   /**
351    * Write out this <code>Column</code>'s accessors.
352    *
353    * @param w
354    *          TableBase
355    * @throws IOException
356    *           if something goes wrong with the file system
357    */
358   public void generateColAccessor(Writer w) throws IOException {
359     w.write("\n /**\n" 
360         + "  * Retrieves the <code>" + capitalisedName + "</code> <code>Column</code> for this \n" 
361         + "  * <code>"+ table.nameFromDsd + "</code> <code>Table</code>.\n" + "  * \n"
362         + "  * see org.melati.poem.prepro.FieldDef#generateColAccessor \n" 
363         + "  * @return the " + name + " <code>Column</code>\n" 
364         + "  */\n");
365     w.write(
366           "  public final Column<"+rawType+"> get" + capitalisedName + "Column() {\n"
367         + "    return col_" + name + ";\n" 
368         + "  }\n");
369   }
370 
371   /**
372    * Write out this <code>Column</code>'s field accessors as part of the
373    * anonymous definition of the <code>Column</code>.
374    *
375    * @param w
376    *          TableBase
377    * @throws IOException
378    *           if something goes wrong with the file system
379    */
380   protected void generateColRawAccessors(Writer w) throws IOException {
381     w.write("          public Object getRaw_unsafe(Persistent g)\n"
382         + "              throws AccessPoemException {\n"
383         + "            return ((" + shortestUnambiguousClassname + ")g)." + "get" + capitalisedName
384         + "_unsafe();\n" + "          }\n" + "\n");
385 
386     w.write("          public void setRaw_unsafe(Persistent g, Object raw)\n"
387         + "              throws AccessPoemException {\n" + "            (("
388         + shortestUnambiguousClassname + ")g).set" + capitalisedName + "_unsafe((" + rawType + ")raw);\n"
389         + "          }\n");
390   }
391 
392   /**
393    * Write out this <code>Column</code>'s definition using an anonymous
394    * class.
395    *
396    * @param w
397    *          TableBase
398    * @throws IOException
399    *           if something goes wrong with the file system
400    */
401   public void generateColDefinition(Writer w) throws IOException {
402     w
403         .write("    defineColumn(col_" + name + " =\n"
404             + "        new Column<"+rawType +">(this, \"" + name + "\",\n"
405             + "                   " + poemTypeJava() + ",\n"
406             + "                   DefinitionSource.dsd) { \n"
407             + "          public Object getCooked(Persistent g)\n"
408             + "              throws AccessPoemException, PoemException {\n"
409             + "            return ((" + shortestUnambiguousClassname + ")g).get" + capitalisedName + "();\n"
410             + "          }\n"
411             + "\n"
412             + "          public void setCooked(Persistent g, Object cooked)\n"
413             + "              throws AccessPoemException, ValidationPoemException {\n"
414             + "            ((" + shortestUnambiguousClassname + ")g).set" + capitalisedName + "((" + typeShortName
415             + ")cooked);\n" + "          }\n" + "\n"
416             + "          public Field<"+rawType+"> asField(Persistent g) {\n"
417             + "            return ((" + shortestUnambiguousClassname + ")g).get" + capitalisedName
418             + "Field();\n" + "          }\n" + "\n");
419 
420     if (isTroidColumn || !isEditable)
421       w.write("          public boolean defaultUserEditable() {\n"
422           + "            return false;\n" + "          }\n" + "\n");
423 
424     if (isTroidColumn || !isCreateable)
425       w.write("          public boolean defaultUserCreateable() {\n"
426           + "            return false;\n" + "          }\n" + "\n");
427 
428     if (displayLevel != null)
429       w.write("          public DisplayLevel defaultDisplayLevel() {\n"
430           + "            return DisplayLevel." + displayLevel + ";\n"
431           + "          }\n" + "\n");
432 
433     if (searchability != null)
434       w.write("          public Searchability defaultSearchability() {\n"
435           + "            return Searchability." + searchability + ";\n"
436           + "          }\n" + "\n");
437 
438     if (displayOrderPriority != -1)
439       w.write("          public Integer defaultDisplayOrderPriority() {\n"
440           + "            return new Integer(" + displayOrderPriority + ");\n"
441           + "          }\n" + "\n");
442 
443     if (sortDescending)
444       w.write("          public boolean defaultSortDescending() {\n"
445           + "            return true;\n" 
446           + "          }\n" 
447           + "\n");
448 
449     if (displayName != null)
450       w.write("          public String defaultDisplayName() {\n"
451           + "            return " + StringUtils.quoted(displayName, '"')
452           + ";\n" 
453           + "          }\n" 
454           + "\n");
455 
456     w.write("          public int defaultDisplayOrder() {\n"
457           + "            return " + displayOrder + ";\n" 
458           + "          }\n"
459           + "\n");
460 
461     if (description != null)
462       w.write("          public String defaultDescription() {\n"
463           + "            return " + StringUtils.quoted(description, '"')
464           + ";\n" + "          }\n" 
465           + "\n");
466 
467     if (isIndexed)
468       w.write("          public boolean defaultIndexed() {\n"
469           + "            return true;\n" 
470           + "          }\n" 
471           + "\n");
472 
473     if (isUnique)
474       w.write("          public boolean defaultUnique() {\n"
475           + "            return true;\n" 
476           + "          }\n" 
477           + "\n");
478 
479     if (width != -1)
480       w.write("          public int defaultWidth() {\n"
481           + "            return " + width + ";\n" 
482           + "          }\n" 
483           + "\n");
484 
485     if (height != -1)
486       w.write("          public int defaultHeight() {\n"
487           + "            return " + height + ";\n" 
488           + "          }\n" 
489           + "\n");
490 
491     if (renderinfo != null)
492       w.write("          public String defaultRenderinfo() {\n"
493           + "            return " + StringUtils.quoted(renderinfo, '"') + ";\n"
494           + "          }\n" 
495           + "\n");
496 
497     generateColRawAccessors(w);
498 
499     w.write("        });\n");
500   }
501 
502   /** @return the Java string for this <code>PoemType</code>. */
503   public abstract String poemTypeJava();
504 
505   /**
506    * @return whether this column is a deleted marker
507    */
508   public boolean isDeletedColumn() {
509     return isDeletedColumn;
510   }
511 
512   /**
513    * Set whether this field represents a deleted marker.
514    *
515    * @param isDeletedColumn boolean
516    */
517   public void setDeletedColumn(boolean isDeletedColumn) {
518     if (this.isDeletedColumn)
519       throw new IllegalityException(lineNumber,
520           "Deleted qualifier already set true.");
521     this.isDeletedColumn = isDeletedColumn;
522   }
523 
524   /**
525    * @return whether this field represents a troid column.
526    */
527   public boolean isTroidColumn() {
528     return isTroidColumn;
529   }
530 
531   /**
532    * Set the isTroidColumn property.
533    *
534    * @param isTroidColumn boolean
535    */
536   public void setTroidColumn(boolean isTroidColumn) {
537     if (this.isTroidColumn)
538       throw new IllegalityException(lineNumber,
539           "Troid qualifier  already set true.");
540     this.isTroidColumn = isTroidColumn;
541   }
542 
543   /**
544    * @return whether this column is nullable.
545    */
546   public boolean isNullable() {
547     return isNullable;
548   }
549 
550   /**
551    * Set the nullable property.
552    *
553    * @param isNullable boolean
554    */
555   public void setNullable(boolean isNullable) {
556     if (this.isNullable)
557       throw new IllegalityException(lineNumber,
558           "Nullable qualifier  already set true.");
559     this.isNullable = isNullable;
560   }
561 
562   /**
563    * @return whether this field is editable
564    */
565   public boolean isEditable() {
566     return isEditable;
567   }
568 
569   /**
570    * Set the isEditable property.
571    *
572    * @param isEditable boolean
573    */
574   public void setEditable(boolean isEditable) {
575     if (!this.isEditable)
576       throw new IllegalityException(lineNumber,
577           "Editable qualifier  already set true.");
578     this.isEditable = isEditable;
579   }
580 
581   /**
582    * @return whether this column shoudl be sorted in descending order
583    */
584   public boolean isSortDescending() {
585     return sortDescending;
586   }
587 
588   /**
589    * Set the sortDescending property.
590    * @param sortDescending whether to sort in a descending order
591    */
592   public void setSortDescending(boolean sortDescending) {
593     if (this.sortDescending)
594       throw new IllegalityException(lineNumber,
595           "Sort descending qualifier  already set true.");
596     this.sortDescending = sortDescending;
597   }
598 
599   /**
600    * @return whether this column is user createable
601    */
602   public boolean isCreateable() {
603     return isCreateable;
604   }
605 
606   /**
607    * Set the isCreatable property.
608    *
609    * @param isCreateable boolean
610    */
611   public void setCreateable(boolean isCreateable) {
612     if (!this.isCreateable)
613       throw new IllegalityException(lineNumber,
614           "Creatable qualifier  already set true.");
615     this.isCreateable = isCreateable;
616   }
617 
618   /**
619    * @return whether this column is indexed.
620    */
621   public boolean isIndexed() {
622     return isIndexed;
623   }
624 
625   /**
626    * Set the isIndexed property.
627    * @param isIndexed boolean
628    */
629   public void setIndexed(boolean isIndexed) {
630     if (this.isIndexed)
631       throw new IllegalityException(lineNumber,
632           "Indexed qualifier  already set true.");
633     this.isIndexed = isIndexed;
634   }
635 
636   /**
637    * @return whether this column is unique
638    */
639   public boolean isUnique() {
640     return isUnique;
641   }
642 
643   /**
644    * Set the isUnique property.
645    *
646    * @param isUnique boolean
647    */
648   public void setUnique(boolean isUnique) {
649     if (this.isUnique)
650       throw new IllegalityException(lineNumber,
651           "Unique qualifier  already set true.");
652     this.isUnique = isUnique;
653   }
654 
655   /**
656    * @return the width
657    */
658   public int getWidth() {
659     return width;
660   }
661 
662   /**
663    * Set the width property.
664    *
665    * @param width the width to set
666    */
667   public void setWidth(int width) {
668     if (this.width != -1)
669       throw new IllegalityException(lineNumber, "Size already set to "
670           + this.width + " cannot overwrite with " + width);
671     this.width = width;
672   }
673 
674   /**
675    * @return the height
676    */
677   public int getHeight() {
678     return height;
679   }
680 
681   /**
682    * Set the height property.
683    *
684    * @param height the height to set
685    */
686   public void setHeight(int height) {
687     if (this.height != -1)
688       throw new IllegalityException(lineNumber, "Height already set to "
689           + this.width + " cannot overwrite with " + width);
690     this.height = height;
691   }
692 }