View Javadoc
1   /*
2   Copyright (c) 2011 James Ahlborn
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.impl;
18  
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.EnumSet;
23  import java.util.HashSet;
24  import java.util.IdentityHashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import com.healthmarketscience.jackcess.ColumnBuilder;
30  import com.healthmarketscience.jackcess.DataType;
31  import com.healthmarketscience.jackcess.IndexBuilder;
32  import com.healthmarketscience.jackcess.PropertyMap;
33  import com.healthmarketscience.jackcess.TableBuilder;
34  
35  /**
36   * Helper class used to maintain state during table creation.
37   *
38   * @author James Ahlborn
39   * @usage _advanced_class_
40   */
41  public class TableCreator extends TableMutator
42  {
43    private String _name;
44    private List<ColumnBuilder> _columns;
45    private List<IndexBuilder> _indexes;
46    private final List<IndexDataState> _indexDataStates = 
47      new ArrayList<>();
48    private final Map<ColumnBuilder,ColumnState> _columnStates = 
49      new IdentityHashMap<>();
50    private final List<ColumnBuilder> _lvalCols = new ArrayList<>();
51    private int _tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
52    private int _umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
53    private int _indexCount;
54    private int _logicalIndexCount;
55  
56    public TableCreator(DatabaseImpl database) {
57      super(database);
58    }
59  
60    public String getName() {
61      return _name;
62    }
63  
64    @Override
65    String getTableName() {
66      return getName();
67    }
68    
69    @Override
70    public int getTdefPageNumber() {
71      return _tdefPageNumber;
72    }
73  
74    public int getUmapPageNumber() {
75      return _umapPageNumber;
76    }
77  
78    public List<ColumnBuilder> getColumns() {
79      return _columns;
80    }
81  
82    public List<IndexBuilder> getIndexes() {
83      return _indexes;
84    }
85  
86    public boolean hasIndexes() {
87      return !_indexes.isEmpty();
88    }
89  
90    public int getIndexCount() {
91      return _indexCount;
92    }
93  
94    public int getLogicalIndexCount() {
95      return _logicalIndexCount;
96    }
97  
98    @Override
99    public IndexDataState getIndexDataState(IndexBuilder idx) {
100     for(IndexDataState idxDataState : _indexDataStates) {
101       for(IndexBuilder curIdx : idxDataState.getIndexes()) {
102         if(idx == curIdx) {
103           return idxDataState;
104         }
105       }
106     }
107     throw new IllegalStateException(withErrorContext(
108         "could not find state for index"));
109   }
110 
111   public List<IndexDataState> getIndexDataStates() {
112     return _indexDataStates;
113   }
114 
115   @Override
116   public ColumnState getColumnState(ColumnBuilder col) {
117     return _columnStates.get(col);
118   }
119 
120   public List<ColumnBuilder> getLongValueColumns() {
121     return _lvalCols;
122   }
123 
124   @Override
125   short getColumnNumber(String colName) {
126     for(ColumnBuilder col : _columns) {
127       if(col.getName().equalsIgnoreCase(colName)) {
128         return col.getColumnNumber();
129       }
130     }
131     return IndexData.COLUMN_UNUSED;
132   }
133 
134   @Override
135   boolean isComplexColumn(String colName) {
136     for(ColumnBuilder col : _columns) {
137       if(col.getName().equalsIgnoreCase(colName)) {
138         return (col.getType() == DataType.COMPLEX_TYPE);
139       }
140     }
141     return false;
142   }
143 
144   /**
145    * @return The number of variable length columns which are not long values
146    *         found in the list
147    * @usage _advanced_method_
148    */
149   public short countNonLongVariableLength() {
150     short rtn = 0;
151     for (ColumnBuilder col : _columns) {
152       if (col.isVariableLength() && !col.getType().isLongValue()) {
153         rtn++;
154       }
155     }
156     return rtn;
157   }
158   
159 
160   /**
161    * Creates the table in the database.
162    * @usage _advanced_method_
163    */
164   public TableImpl createTable(TableBuilder table) throws IOException {
165 
166     _name = table.getName();
167     _columns = table.getColumns();
168     _indexes = table.getIndexes();
169     if(_indexes == null) {
170       _indexes = Collections.<IndexBuilder>emptyList();
171     }
172 
173     validate();
174 
175     // assign column numbers and do some assorted column bookkeeping
176     short columnNumber = (short) 0;
177     for(ColumnBuilder col : _columns) {
178       col.setColumnNumber(columnNumber++);
179       if(col.getType().isLongValue()) {
180         _lvalCols.add(col);
181         // only lval columns need extra state
182         _columnStates.put(col, new ColumnState());
183       }
184     }
185 
186     if(hasIndexes()) {
187       // sort out index numbers (and backing index data).  
188       for(IndexBuilder idx : _indexes) {
189         idx.setIndexNumber(_logicalIndexCount++);
190         findIndexDataState(idx);
191       }
192     }
193 
194     getPageChannel().startExclusiveWrite();
195     try {
196       
197       // reserve some pages
198       _tdefPageNumber = reservePageNumber();
199       _umapPageNumber = reservePageNumber();
200     
201       //Write the tdef page to disk.
202       TableImpl.writeTableDefinition(this);
203 
204       // update the database with the new table info
205       getDatabase().addNewTable(_name, _tdefPageNumber, DatabaseImpl.TYPE_TABLE, 
206                                 null, null);
207 
208       TableImpl newTable = getDatabase().getTable(_name);
209 
210       // add any table properties
211       boolean addedProps = false;
212       Map<String,PropertyMap.Property> props = table.getProperties();
213       if(props != null) {
214         newTable.getProperties().putAll(props.values());
215         addedProps = true;
216       }
217       for(ColumnBuilder cb : _columns) {
218         Map<String,PropertyMap.Property> colProps = cb.getProperties();
219         if(colProps != null) {
220           newTable.getColumn(cb.getName()).getProperties()
221             .putAll(colProps.values());
222           addedProps = true;
223         }
224       }
225 
226       // all table and column props are saved together
227       if(addedProps) {
228         newTable.getProperties().save();
229       }
230 
231       return newTable;
232 
233     } finally {
234       getPageChannel().finishWrite();
235     }
236   }
237 
238   private IndexDataState findIndexDataState(IndexBuilder idx) {
239 
240     // search for an index which matches the given index (in terms of the
241     // backing data)
242     for(IndexDataState idxDataState : _indexDataStates) {
243       if(sameIndexData(idxDataState.getFirstIndex(), idx)) {
244         idxDataState.addIndex(idx);
245         return idxDataState;
246       }
247     }
248 
249     // no matches found, need new index data state
250     IndexDataState idxDataState = new IndexDataState();
251     idxDataState.setIndexDataNumber(_indexCount++);
252     idxDataState.addIndex(idx);
253     _indexDataStates.add(idxDataState);
254     return idxDataState;
255   }
256 
257   /**
258    * Validates the new table information before attempting creation.
259    */
260   private void validate() throws IOException {
261 
262     getDatabase().validateNewTableName(_name);
263     
264     if((_columns == null) || _columns.isEmpty()) {
265       throw new IllegalArgumentException(withErrorContext(
266           "Cannot create table with no columns"));
267     }
268     if(_columns.size() > getFormat().MAX_COLUMNS_PER_TABLE) {
269       throw new IllegalArgumentException(withErrorContext(
270           "Cannot create table with more than " +
271           getFormat().MAX_COLUMNS_PER_TABLE + " columns"));
272     }
273     
274     Set<String> colNames = new HashSet<>();
275     // next, validate the column definitions
276     for(ColumnBuilder column : _columns) {
277       validateColumn(colNames, column);
278     }
279 
280     List<ColumnBuilder> autoCols = getAutoNumberColumns();
281     if(autoCols.size() > 1) {
282       // for most autonumber types, we can only have one of each type
283       Set<DataType> autoTypes = EnumSet.noneOf(DataType.class);
284       for(ColumnBuilder c : autoCols) {
285         validateAutoNumberColumn(autoTypes, c);
286       }
287     }
288 
289     if(hasIndexes()) {
290 
291       if(_indexes.size() > getFormat().MAX_INDEXES_PER_TABLE) {
292         throw new IllegalArgumentException(withErrorContext(
293             "Cannot create table with more than " +
294             getFormat().MAX_INDEXES_PER_TABLE + " indexes"));
295       }
296 
297       // now, validate the indexes
298       Set<String> idxNames = new HashSet<>();
299       boolean foundPk[] = new boolean[1];
300       for(IndexBuilder index : _indexes) {
301         validateIndex(colNames, idxNames, foundPk, index);
302       }
303     }
304   }
305 
306   private List<ColumnBuilder> getAutoNumberColumns() 
307   {
308     List<ColumnBuilder> autoCols = new ArrayList<>(1);
309     for(ColumnBuilder c : _columns) {
310       if(c.isAutoNumber()) {
311         autoCols.add(c);
312       }
313     }
314     return autoCols;
315   }
316 
317   private static boolean sameIndexData(IndexBuilder/../../../com/healthmarketscience/jackcess/IndexBuilder.html#IndexBuilder">IndexBuilder idx1, IndexBuilder idx2) {
318     // index data can be combined if flags match and columns (and col flags)
319     // match
320     if(idx1.getFlags() != idx2.getFlags()) {
321       return false;
322     }
323 
324     if(idx1.getColumns().size() != idx2.getColumns().size()) {
325       return false;
326     }
327     
328     for(int i = 0; i < idx1.getColumns().size(); ++i) {
329       IndexBuilder.Column col1 = idx1.getColumns().get(i);
330       IndexBuilder.Column col2 = idx2.getColumns().get(i);
331 
332       if(!sameIndexData(col1, col2)) {
333         return false;
334       }
335     }
336 
337     return true;
338   }
339 
340   private static boolean sameIndexData(
341       IndexBuilder.Column col1, IndexBuilder.Column col2) {
342     return (col1.getName().equals(col2.getName()) && 
343             (col1.getFlags() == col2.getFlags()));
344   }
345 
346   @Override
347   protected String withErrorContext(String msg) {
348     return msg + "(Table=" + getName() + ")";
349   }
350 }