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.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import com.healthmarketscience.jackcess.impl.DatabaseImpl;
31  import com.healthmarketscience.jackcess.impl.PropertyMapImpl;
32  import com.healthmarketscience.jackcess.impl.TableCreator;
33  import com.healthmarketscience.jackcess.impl.ToStringBuilder;
34  
35  /**
36   * Builder style class for constructing a {@link Table}.
37   * <p>
38   * Example:
39   * <pre>
40   *   Table table = new TableBuilder("Test")
41   *     .addColumn(new ColumnBuilder("ID", DataType.LONG)
42   *                .setAutoNumber(true))
43   *     .addColumn(new ColumnBuilder("Name", DataType.TEXT))
44   *     .addIndex(new IndexBuilder(IndexBuilder.PRIMARY_KEY_NAME)
45   *               .addColumns("ID").setPrimaryKey())
46   *     .toTable(db);
47   * </pre>
48   *
49   * @author James Ahlborn
50   * @see ColumnBuilder
51   * @see IndexBuilder
52   * @see RelationshipBuilder
53   * @usage _general_class_
54   */
55  public class TableBuilder {
56  
57    /** Prefix for column or table names that are reserved words */
58    private static final String ESCAPE_PREFIX = "x";
59  
60    /* nested class for lazy loading */
61    private static final class ReservedWords {
62      /**
63       * All of the reserved words in Access that should be escaped when creating
64       * table or column names
65       */
66      private static final Set<String> VALUES =
67        new HashSet<>(Arrays.asList(
68         "add", "all", "alphanumeric", "alter", "and", "any", "application", "as",
69         "asc", "assistant", "autoincrement", "avg", "between", "binary", "bit",
70         "boolean", "by", "byte", "char", "character", "column", "compactdatabase",
71         "constraint", "container", "count", "counter", "create", "createdatabase",
72         "createfield", "creategroup", "createindex", "createobject", "createproperty",
73         "createrelation", "createtabledef", "createuser", "createworkspace",
74         "currency", "currentuser", "database", "date", "datetime", "delete",
75         "desc", "description", "disallow", "distinct", "distinctrow", "document",
76         "double", "drop", "echo", "else", "end", "eqv", "error", "exists", "exit",
77         "false", "field", "fields", "fillcache", "float", "float4", "float8",
78         "foreign", "form", "forms", "from", "full", "function", "general",
79         "getobject", "getoption", "gotopage", "group", "group by", "guid", "having",
80         "idle", "ieeedouble", "ieeesingle", "if", "ignore", "imp", "in", "index",
81         "indexes", "inner", "insert", "inserttext", "int", "integer", "integer1",
82         "integer2", "integer4", "into", "is", "join", "key", "lastmodified", "left",
83         "level", "like", "logical", "logical1", "long", "longbinary", "longtext",
84         "macro", "match", "max", "min", "mod", "memo", "module", "money", "move",
85         "name", "newpassword", "no", "not", "null", "number", "numeric", "object",
86         "oleobject", "off", "on", "openrecordset", "option", "or", "order", "outer",
87         "owneraccess", "parameter", "parameters", "partial", "percent", "pivot",
88         "primary", "procedure", "property", "queries", "query", "quit", "real",
89         "recalc", "recordset", "references", "refresh", "refreshlink",
90         "registerdatabase", "relation", "repaint", "repairdatabase", "report",
91         "reports", "requery", "right", "screen", "section", "select", "set",
92         "setfocus", "setoption", "short", "single", "smallint", "some", "sql",
93         "stdev", "stdevp", "string", "sum", "table", "tabledef", "tabledefs",
94         "tableid", "text", "time", "timestamp", "top", "transform", "true", "type",
95         "union", "unique", "update", "user", "value", "values", "var", "varp",
96         "varbinary", "varchar", "where", "with", "workspace", "xor", "year", "yes",
97         "yesno"));
98    }
99  
100 
101   /** name of the new table */
102   private String _name;
103   /** columns for the new table */
104   private final List<ColumnBuilder> _columns = new ArrayList<>();
105   /** indexes for the new table */
106   private final List<IndexBuilder> _indexes = new ArrayList<>();
107   /** whether or not table/column/index names are automatically escaped */
108   private boolean _escapeIdentifiers;
109   /** table properties (if any) */
110   private Map<String,PropertyMap.Property> _props;
111 
112 
113   public TableBuilder(String name) {
114     this(name, false);
115   }
116 
117   public TableBuilder(String name, boolean escapeIdentifiers) {
118     _name = name;
119     _escapeIdentifiers = escapeIdentifiers;
120     if(_escapeIdentifiers) {
121       _name = escapeIdentifier(_name);
122     }
123   }
124 
125   public String getName() {
126     return _name;
127   }
128 
129   /**
130    * Adds a Column to the new table.
131    */
132   public TableBuilder addColumn(ColumnBuilder column) {
133     if(_escapeIdentifiers) {
134       column.escapeName();
135     }
136     _columns.add(column);
137     return this;
138   }
139 
140   /**
141    * Adds the Columns to the new table.
142    */
143   public TableBuilder addColumns(Collection<? extends ColumnBuilder> columns) {
144     if(columns != null) {
145       for(ColumnBuilder col : columns) {
146         addColumn(col);
147       }
148     }
149     return this;
150   }
151 
152   public List<ColumnBuilder> getColumns() {
153     return _columns;
154   }
155 
156   /**
157    * Adds an IndexBuilder to the new table.
158    */
159   public TableBuilder addIndex(IndexBuilder index) {
160     if(_escapeIdentifiers) {
161       index.setName(escapeIdentifier(index.getName()));
162       for(IndexBuilder.Column col : index.getColumns()) {
163         col.setName(escapeIdentifier(col.getName()));
164       }
165     }
166     _indexes.add(index);
167     return this;
168   }
169 
170   /**
171    * Adds the Indexes to the new table.
172    */
173   public TableBuilder addIndexes(Collection<? extends IndexBuilder> indexes) {
174     if(indexes != null) {
175       for(IndexBuilder col : indexes) {
176         addIndex(col);
177       }
178     }
179     return this;
180   }
181 
182   public List<IndexBuilder> getIndexes() {
183     return _indexes;
184   }
185 
186   /**
187    * Sets whether or not subsequently added columns will have their names
188    * automatically escaped
189    */
190   public TableBuilder setEscapeIdentifiers(boolean escapeIdentifiers) {
191     _escapeIdentifiers = escapeIdentifiers;
192     return this;
193   }
194 
195   /**
196    * Sets the names of the primary key columns for this table.  Convenience
197    * method for creating a primary key index on a table.
198    */
199   public TableBuilder setPrimaryKey(String... colNames) {
200     addIndex(new IndexBuilder(IndexBuilder.PRIMARY_KEY_NAME)
201              .addColumns(colNames).setPrimaryKey());
202     return this;
203   }
204 
205   /**
206    * Escapes the new table's name using {@link TableBuilder#escapeIdentifier}.
207    */
208   public TableBuilder escapeName() {
209     _name = escapeIdentifier(_name);
210     return this;
211   }
212 
213   /**
214    * Sets the table property with the given name to the given value.  Attempts
215    * to determine the type of the property (see
216    * {@link PropertyMap#put(String,Object)} for details on determining the
217    * property type).
218    */
219   public TableBuilder putProperty(String name, Object value) {
220     return putProperty(name, null, value);
221   }
222 
223   /**
224    * Sets the table property with the given name and type to the given value.
225    */
226   public TableBuilder putProperty(String name, DataType type, Object value) {
227     if(_props == null) {
228       _props = new HashMap<>();
229     }
230     _props.put(name, PropertyMapImpl.createProperty(name, type, value));
231     return this;
232   }
233 
234   public Map<String,PropertyMap.Property> getProperties() {
235     return _props;
236   }
237 
238   /**
239    * Creates a new Table in the given Database with the currently configured
240    * attributes.
241    */
242   public Table toTable(Database db) throws IOException {
243     return new TableCreator(((DatabaseImpl)db)).createTable(this);
244   }
245 
246   @Override
247   public String toString() {
248     return ToStringBuilder.builder(this)
249       .append("name", _name)
250       .append("escapeIdentifiers", _escapeIdentifiers)
251       .append("columns", _columns)
252       .append("indexes", _indexes)
253       .appendIfNotNull("props", _props)
254       .toString();
255   }
256 
257   /**
258    * @return A table or column name escaped for Access
259    * @usage _general_method_
260    */
261   public static String escapeIdentifier(String s) {
262     if (isReservedWord(s)) {
263       return ESCAPE_PREFIX + s;
264     }
265     return s;
266   }
267 
268   /**
269    * @return {@code true} if the given string is a reserved word,
270    *         {@code false} otherwise
271    * @usage _general_method_
272    */
273   public static boolean isReservedWord(String s) {
274     return ReservedWords.VALUES.contains(s.toLowerCase(Locale.ROOT));
275   }
276 
277 }