View Javadoc
1   /*
2   Copyright (c) 2008 Health Market Science, Inc.
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess;
18  
19  import java.io.IOException;
20  import java.sql.SQLException;
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  import com.healthmarketscience.jackcess.impl.ColumnImpl;
25  import com.healthmarketscience.jackcess.impl.DatabaseImpl;
26  import com.healthmarketscience.jackcess.impl.JetFormat;
27  import com.healthmarketscience.jackcess.impl.PropertyMapImpl;
28  import com.healthmarketscience.jackcess.impl.TableImpl;
29  import com.healthmarketscience.jackcess.impl.TableUpdater;
30  
31  /**
32   * Builder style class for constructing a {@link Column}.  See {@link
33   * TableBuilder} for example usage.  Additionally, a Column can be added to an
34   * existing Table using the {@link #addToTable(Table)} method.
35   *
36   * @author James Ahlborn
37   * @see TableBuilder
38   * @usage _general_class_
39   */
40  public class ColumnBuilder {
41  
42    /** name of the new column */
43    private String _name;
44    /** the type of the new column */
45    private DataType _type;
46    /** optional length for the new column */
47    private Short _length;
48    /** optional precision for the new column */
49    private Byte _precision;
50    /** optional scale for the new column */
51    private Byte _scale;
52    /** whether or not the column is auto-number */
53    private boolean _autoNumber;
54    /** whether or not the column allows compressed unicode */
55    private boolean _compressedUnicode;
56    /** whether or not the column is calculated */
57    private boolean _calculated;
58    /** whether or not the column is a hyperlink (memo only) */
59    private boolean _hyperlink;
60    /** 0-based column number */
61    private short _columnNumber;
62    /** the collating sort order for a text field */
63    private ColumnImpl.SortOrder _sortOrder;
64    /** table properties (if any) */
65    private Map<String,PropertyMap.Property> _props;
66  
67  
68    public ColumnBuilder(String name) {
69      this(name, null);
70    }
71    
72    public ColumnBuilder(String name, DataType type) {
73      _name = name;
74      _type = type;
75    }
76  
77    public String getName() {
78      return _name;
79    }
80  
81    /**
82     * Sets the type for the new column.
83     */
84    public ColumnBuilder setType(DataType type) {
85      _type = type;
86      return this;
87    }
88  
89    public DataType getType() {
90      return _type;
91    }
92  
93    /**
94     * Sets the type for the new column based on the given SQL type.
95     */
96    public ColumnBuilder setSQLType(int type) throws SQLException {
97      return setSQLType(type, 0);
98    }
99    
100   /**
101    * Sets the type for the new column based on the given SQL type and target
102    * data length (in type specific units).
103    */
104   public ColumnBuilder setSQLType(int type, int lengthInUnits)
105     throws SQLException
106   {
107     return setType(DataType.fromSQLType(type, lengthInUnits));
108   }
109 
110   /**
111    * Sets the precision for the new column.
112    */
113   public ColumnBuilder setPrecision(int newPrecision) {
114     _precision = (byte)newPrecision;
115     return this;
116   }
117 
118   public byte getPrecision() {
119     return ((_precision != null) ? _precision : (byte)_type.getDefaultPrecision());
120   }
121 
122   /**
123    * Sets the precision for the new column to the max length for the type.
124    * Does nothing for types which do not have a precision.
125    */
126   public ColumnBuilder setMaxPrecision() {
127     if(_type.getHasScalePrecision()) {
128       setPrecision(_type.getMaxPrecision());
129     }
130     return this;
131   }
132 
133   /**
134    * Sets the scale for the new column.
135    */
136   public ColumnBuilder setScale(int newScale) {
137     _scale = (byte)newScale;
138     return this;
139   }
140 
141   public byte getScale() {
142     return ((_scale != null) ? _scale : (byte)_type.getDefaultScale());
143   }
144 
145   /**
146    * Sets the scale for the new column to the max length for the type.  Does
147    * nothing for types which do not have a scale.
148    */
149   public ColumnBuilder setMaxScale() {
150     if(_type.getHasScalePrecision()) {
151       setScale(_type.getMaxScale());
152     }
153     return this;
154   }
155 
156   /**
157    * Sets the length (in bytes) for the new column.
158    */
159   public ColumnBuilder setLength(int length) {
160     _length = (short)length;
161     return this;
162   }
163 
164   public short getLength() {
165     return ((_length != null) ? _length :
166             (short)(!_type.isVariableLength() ? _type.getFixedSize() :
167                     _type.getDefaultSize()));
168   }
169 
170   /**
171    * Sets the length (in type specific units) for the new column.
172    */
173   public ColumnBuilder setLengthInUnits(int unitLength) {
174     return setLength(_type.getUnitSize() * unitLength);
175   }
176   
177   /**
178    * Sets the length for the new column to the max length for the type.  Does
179    * nothing for types which are not variable length.
180    */
181   public ColumnBuilder setMaxLength() {
182     // length setting only makes sense for variable length columns
183     if(_type.isVariableLength()) {
184       setLength(_type.getMaxSize());
185     }
186     return this;
187   }
188   
189   /**
190    * Sets whether of not the new column is an auto-number column.
191    */
192   public ColumnBuilder setAutoNumber(boolean autoNumber) {
193     _autoNumber = autoNumber;
194     return this;
195   }
196 
197   public boolean isAutoNumber() {
198     return _autoNumber;
199   }
200 
201   /**
202    * Sets whether of not the new column allows unicode compression.
203    */
204   public ColumnBuilder setCompressedUnicode(boolean compressedUnicode) {
205     _compressedUnicode = compressedUnicode;
206     return this;
207   }
208 
209   public boolean isCompressedUnicode() {
210     return _compressedUnicode;
211   }
212 
213   /**
214    * Sets whether of not the new column is a calculated column.
215    */
216   public ColumnBuilder setCalculated(boolean calculated) {
217     _calculated = calculated;
218     return this;
219   }
220 
221   public boolean isCalculated() {
222     return _calculated;
223   }
224 
225   /**
226    * Convenience method to set the various info for a calculated type (flag,
227    * result type property and expression)
228    */
229   public ColumnBuilder setCalculatedInfo(String expression) {
230     setCalculated(true);
231     putProperty(PropertyMap.EXPRESSION_PROP, expression);
232     return putProperty(PropertyMap.RESULT_TYPE_PROP, getType().getValue());
233   }
234 
235   public boolean isVariableLength() {
236     // calculated columns are written as var len
237     return(getType().isVariableLength() || isCalculated());
238   }
239 
240   /**
241    * Sets whether of not the new column allows unicode compression.
242    */
243   public ColumnBuilder setHyperlink(boolean hyperlink) {
244     _hyperlink = hyperlink;
245     return this;
246   }
247 
248   public boolean isHyperlink() {
249     return _hyperlink;
250   }
251 
252   /**
253    * Sets the column property with the given name to the given value.  Attempts
254    * to determine the type of the property (see
255    * {@link PropertyMap#put(String,Object)} for details on determining the
256    * property type).
257    */
258   public ColumnBuilder putProperty(String name, Object value) {
259     return putProperty(name, null, value);
260   }
261 
262   /**
263    * Sets the column property with the given name and type to the given value.
264    */
265   public ColumnBuilder putProperty(String name, DataType type, Object value) {
266     setProperty(name, PropertyMapImpl.createProperty(name, type, value));
267     return this;
268   }
269 
270   public Map<String,PropertyMap.Property> getProperties() {
271     return _props;
272   }
273 
274   private void setProperty(String name, PropertyMap.Property prop) {
275     if(prop == null) {
276       return;
277     }
278     if(_props == null) {
279       _props = new HashMap<String,PropertyMap.Property>();
280     }
281     _props.put(name, prop);
282   }
283 
284   private PropertyMap.Property getProperty(String name) {
285     return ((_props != null) ? _props.get(name) : null);
286   }
287   
288   /**
289    * Sets all attributes except name from the given Column template (including
290    * all column properties except GUID).
291    */
292   public ColumnBuilder setFromColumn(Column template) 
293     throws IOException
294   {
295     DataType type = template.getType();
296     setType(type);
297     setLength(template.getLength());
298     setAutoNumber(template.isAutoNumber());
299     if(type.getHasScalePrecision()) {
300       setScale(template.getScale());
301       setPrecision(template.getPrecision());
302     }
303     setCalculated(template.isCalculated());
304     setCompressedUnicode(template.isCompressedUnicode());
305     setHyperlink(template.isHyperlink());
306     if(template instanceof ColumnImpl) {
307       setTextSortOrder(((ColumnImpl)template).getTextSortOrder());
308     }
309 
310     PropertyMap colProps = template.getProperties();
311     for(PropertyMap.Property colProp : colProps) {
312       // copy everything but guid
313       if(!PropertyMap.GUID_PROP.equalsIgnoreCase(colProp.getName())) {
314         setProperty(colProp.getName(), colProp);
315       }
316     }
317     
318     return this;
319   }
320 
321   /**
322    * Sets all attributes except name from the given Column template.
323    */
324   public ColumnBuilder setFromColumn(ColumnBuilder template) {
325     DataType type = template.getType();
326     _type = type;
327     _length = template._length;
328     _autoNumber = template._autoNumber;
329     if(type.getHasScalePrecision()) {
330       _scale = template._scale;
331       _precision = template._precision;
332     }
333     _calculated = template._calculated;
334     _compressedUnicode = template._compressedUnicode;
335     _hyperlink = template._hyperlink;
336     _sortOrder = template._sortOrder;
337 
338     if(template._props != null) {
339       _props = new HashMap<String,PropertyMap.Property>(template._props);
340     }
341     
342     return this;
343   }
344 
345   /**
346    * Escapes the new column's name using {@link TableBuilder#escapeIdentifier}.
347    */
348   public ColumnBuilder escapeName() {
349     _name = TableBuilder.escapeIdentifier(_name);
350     return this;
351   }
352 
353   /**
354    * @usage _advanced_method_
355    */
356   public short getColumnNumber() {
357     return _columnNumber;
358   }
359 
360   /**
361    * @usage _advanced_method_
362    */
363   public void setColumnNumber(short newColumnNumber) {
364     _columnNumber = newColumnNumber;
365   }
366 
367   /**
368    * @usage _advanced_method_
369    */
370   public ColumnImpl.SortOrder getTextSortOrder() {
371     return _sortOrder;
372   }
373 
374   /**
375    * @usage _advanced_method_
376    */
377   public void setTextSortOrder(ColumnImpl.SortOrder newTextSortOrder) {
378     _sortOrder = newTextSortOrder;
379   }
380 
381   /**
382    * Checks that this column definition is valid.
383    *
384    * @throws IllegalArgumentException if this column definition is invalid.
385    * @usage _advanced_method_
386    */
387   public void validate(JetFormat format) {
388     DatabaseImpl.validateIdentifierName(
389         getName(), format.MAX_COLUMN_NAME_LENGTH, "column");
390 
391     if(getType() == null) {
392       throw new IllegalArgumentException(withErrorContext("must have type"));
393     }
394     if(getType().isUnsupported()) {
395       throw new IllegalArgumentException(withErrorContext(
396           "Cannot create column with unsupported type " + getType()));
397     }
398     if(!format.isSupportedDataType(getType())) {
399       throw new IllegalArgumentException(withErrorContext(
400           "Database format " + format + " does not support type " + getType()));
401     }
402     
403     if(!getType().isVariableLength()) {
404       if(getLength() < getType().getFixedSize()) {
405         throw new IllegalArgumentException(withErrorContext(
406             "Invalid fixed length size " + getLength()));
407       }
408     } else if(!getType().isLongValue()) {
409       if(!getType().isValidSize(getLength())) {
410         throw new IllegalArgumentException(withErrorContext(
411             "Var length must be from " + getType().getMinSize() + " to " +
412             getType().getMaxSize() + " inclusive, found " + getLength()));
413       }
414     }
415 
416     if(getType().getHasScalePrecision()) {
417       if(!getType().isValidScale(getScale())) {
418         throw new IllegalArgumentException(withErrorContext(
419             "Scale must be from " + getType().getMinScale() + " to " +
420             getType().getMaxScale() + " inclusive, found " + getScale()));
421       }
422       if(!getType().isValidPrecision(getPrecision())) {
423         throw new IllegalArgumentException(withErrorContext(
424             "Precision must be from " + getType().getMinPrecision() + " to " +
425             getType().getMaxPrecision() + " inclusive, found " + 
426             getPrecision()));
427       }
428     }
429 
430     if(isAutoNumber()) {
431       if(!getType().mayBeAutoNumber()) {
432         throw new IllegalArgumentException(withErrorContext(
433             "Auto number column must be long integer or guid"));
434       }
435     }
436 
437     if(isCompressedUnicode()) {
438       if(!getType().isTextual()) {
439         throw new IllegalArgumentException(withErrorContext(
440             "Only textual columns allow unicode compression (text/memo)"));
441       }
442     }
443 
444     if(isHyperlink()) {
445       if(getType() != DataType.MEMO) {
446         throw new IllegalArgumentException(withErrorContext(
447             "Only memo columns can be hyperlinks"));
448       }
449     }
450 
451     if(isCalculated()) {
452       if(!format.isSupportedCalculatedDataType(getType())) {
453         throw new IllegalArgumentException(withErrorContext(
454             "Database format " + format + " does not support calculated type " +
455             getType()));
456       }
457 
458       // must have an expression
459       if(getProperty(PropertyMap.EXPRESSION_PROP) == null) {
460         throw new IllegalArgumentException(withErrorContext(
461             "No expression provided for calculated type " + getType()));
462       }
463 
464       // must have result type (just fill in if missing)
465       if(getProperty(PropertyMap.RESULT_TYPE_PROP) == null) {
466         putProperty(PropertyMap.RESULT_TYPE_PROP, getType().getValue());
467       }
468     }
469   }
470 
471   /**
472    * Creates a new Column with the currently configured attributes.
473    */
474   public ColumnBuilder toColumn() {
475     // for backwards compat w/ old code
476     return this;
477   }
478 
479   /**
480    * Adds a new Column to the given Table with the currently configured
481    * attributes.
482    */
483   public Column addToTable(Table table) throws IOException {
484       return new TableUpdater((TableImpl)table).addColumn(this);
485   }
486 
487   private String withErrorContext(String msg) {
488     return msg + "(Column=" + getName() + ")";
489   }
490 }