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.complex;
18  
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  
27  import com.healthmarketscience.jackcess.Column;
28  import com.healthmarketscience.jackcess.DataType;
29  import com.healthmarketscience.jackcess.Database;
30  import com.healthmarketscience.jackcess.IndexCursor;
31  import com.healthmarketscience.jackcess.Row;
32  import com.healthmarketscience.jackcess.RowId;
33  import com.healthmarketscience.jackcess.RuntimeIOException;
34  import com.healthmarketscience.jackcess.Table;
35  import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
36  import com.healthmarketscience.jackcess.complex.ComplexDataType;
37  import com.healthmarketscience.jackcess.complex.ComplexValue;
38  import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
39  import com.healthmarketscience.jackcess.impl.ColumnImpl;
40  import com.healthmarketscience.jackcess.impl.CustomToStringStyle;
41  import com.healthmarketscience.jackcess.impl.TableImpl;
42  
43  /**
44   * Base class for the additional information tracked for complex columns.
45   *
46   * @author James Ahlborn
47   */
48  public abstract class ComplexColumnInfoImpl<V extends ComplexValue> 
49    implements ComplexColumnInfo<V>
50  {
51    private static final int INVALID_ID_VALUE = -1;
52    public static final ComplexValue.Id INVALID_ID = new ComplexValueIdImpl(
53        INVALID_ID_VALUE, null);
54    public static final ComplexValueForeignKey INVALID_FK =
55      new ComplexValueForeignKeyImpl(null, INVALID_ID_VALUE);
56  
57    private final Column _column;
58    private final int _complexTypeId;
59    private final Table _flatTable;
60    private final List<Column> _typeCols;
61    private final Column _pkCol;
62    private final Column _complexValFkCol;
63    private IndexCursor _complexValIdCursor;
64    
65    protected ComplexColumnInfoImpl(Column column, int complexTypeId,
66                                    Table typeObjTable, Table flatTable)
67      throws IOException
68    {
69      _column = column;
70      _complexTypeId = complexTypeId;
71      _flatTable = flatTable;
72      
73      // the flat table has all the "value" columns and 2 extra columns, a
74      // primary key for each row, and a LONG value which is essentially a
75      // foreign key to the main table.
76      List<Column> typeCols = new ArrayList<Column>();
77      List<Column> otherCols = new ArrayList<Column>();
78      diffFlatColumns(typeObjTable, flatTable, typeCols, otherCols);
79  
80      _typeCols = Collections.unmodifiableList(typeCols);
81  
82      Column pkCol = null;
83      Column complexValFkCol = null;
84      for(Column col : otherCols) {
85        if(col.isAutoNumber()) {
86          pkCol = col;
87        } else if(col.getType() == DataType.LONG) {
88          complexValFkCol = col;
89        }
90      }
91  
92      if((pkCol == null) || (complexValFkCol == null)) {
93        throw new IOException("Could not find expected columns in flat table " +
94                              flatTable.getName() + " for complex column with id "
95                              + complexTypeId);
96      }
97      _pkCol = pkCol;
98      _complexValFkCol = complexValFkCol;
99    }
100 
101   public void postTableLoadInit() throws IOException {
102     // nothing to do in base class
103   }
104   
105   public Column getColumn() {
106     return _column;
107   }
108 
109   public Database getDatabase() {
110     return getColumn().getDatabase();
111   }
112 
113   public Column getPrimaryKeyColumn() {
114     return _pkCol;
115   }
116 
117   public Column getComplexValueForeignKeyColumn() {
118     return _complexValFkCol;
119   }
120 
121   protected List<Column> getTypeColumns() {
122     return _typeCols;
123   }
124   
125   public int countValues(int complexValueFk) throws IOException {
126     return getRawValues(complexValueFk,
127                         Collections.singleton(_complexValFkCol.getName()))
128       .size();
129   }
130   
131   public List<Row> getRawValues(int complexValueFk)
132     throws IOException
133   {
134     return getRawValues(complexValueFk, null);
135   }
136 
137   private Iterator<Row> getComplexValFkIter(
138       int complexValueFk, Collection<String> columnNames)
139     throws IOException
140   {
141     if(_complexValIdCursor == null) {
142       _complexValIdCursor = _flatTable.newCursor()
143         .setIndexByColumns(_complexValFkCol)
144         .toIndexCursor();
145     }
146 
147     return _complexValIdCursor.newEntryIterable(complexValueFk)
148       .setColumnNames(columnNames).iterator();
149   }
150   
151   public List<Row> getRawValues(int complexValueFk,
152                                 Collection<String> columnNames)
153     throws IOException
154   {
155     Iterator<Row> entryIter =
156       getComplexValFkIter(complexValueFk, columnNames);
157     if(!entryIter.hasNext()) {
158       return Collections.emptyList();
159     }
160 
161     List<Row> values = new ArrayList<Row>();
162     while(entryIter.hasNext()) {
163       values.add(entryIter.next());
164     }
165     
166     return values;
167   }
168 
169   public List<V> getValues(ComplexValueForeignKey complexValueFk)
170     throws IOException
171   {
172     List<Row> rawValues = getRawValues(complexValueFk.get());
173     if(rawValues.isEmpty()) {
174       return Collections.emptyList();
175     }
176 
177     return toValues(complexValueFk, rawValues);
178   }
179   
180   protected List<V> toValues(ComplexValueForeignKey complexValueFk,
181                              List<Row> rawValues)
182     throws IOException
183   {
184     List<V> values = new ArrayList<V>();
185     for(Row rawValue : rawValues) {
186       values.add(toValue(complexValueFk, rawValue));
187     }
188 
189     return values;
190   }
191 
192   public ComplexValue.Id addRawValue(Map<String,?> rawValue)
193     throws IOException 
194   {
195     Object[] row = ((TableImpl)_flatTable).asRowWithRowId(rawValue);
196     _flatTable.addRow(row);
197     return getValueId(row);
198   }
199 
200   public ComplexValue.Id addValue(V value) throws IOException {
201     Object[] row = asRow(newRowArray(), value);
202     _flatTable.addRow(row);
203     ComplexValue.Id id = getValueId(row);
204     value.setId(id);
205     return id;
206   }
207 
208   public void addValues(Collection<? extends V> values) throws IOException {
209     for(V value : values) {
210       addValue(value);
211     }
212   }
213 
214   public ComplexValue.Id updateRawValue(Row rawValue) throws IOException {
215     _flatTable.updateRow(rawValue);
216     return getValueId(rawValue);
217   }
218   
219   public ComplexValue.Id updateValue(V value) throws IOException {
220     ComplexValue.Id id = value.getId();
221     updateRow(id, asRow(newRowArray(), value));
222     return id;
223   }
224 
225   public void updateValues(Collection<? extends V> values) throws IOException {
226     for(V value : values) {
227       updateValue(value);
228     }
229   }
230 
231   public void deleteRawValue(Row rawValue) throws IOException {
232     deleteRow(rawValue.getId());
233   }
234   
235   public void deleteValue(V value) throws IOException {
236     deleteRow(value.getId().getRowId());
237   }
238 
239   public void deleteValues(Collection<? extends V> values) throws IOException {
240     for(V value : values) {
241       deleteValue(value);
242     }
243   }
244 
245   public void deleteAllValues(int complexValueFk) throws IOException {
246     Iterator<Row> entryIter =
247       getComplexValFkIter(complexValueFk, Collections.<String>emptySet());
248     try {
249       while(entryIter.hasNext()) {
250         entryIter.next();
251         entryIter.remove();
252       }
253     } catch(RuntimeIOException e) {
254       throw (IOException)e.getCause();
255     }
256   }
257 
258   public void deleteAllValues(ComplexValueForeignKey complexValueFk)
259     throws IOException
260   {
261     deleteAllValues(complexValueFk.get());
262   }
263 
264   private void updateRow(ComplexValue.Id id, Object[] row) throws IOException {
265     ((TableImpl)_flatTable).updateRow(id.getRowId(), row);
266   }
267   
268   private void deleteRow(RowId rowId) throws IOException {
269     ((TableImpl)_flatTable).deleteRow(rowId);
270   }
271   
272   protected ComplexValueIdImpl getValueId(Row row) {
273     int idVal = (Integer)getPrimaryKeyColumn().getRowValue(row);
274     return new ComplexValueIdImpl(idVal, row.getId());
275   }
276 
277   protected ComplexValueIdImpl getValueId(Object[] row) {
278     int idVal = (Integer)getPrimaryKeyColumn().getRowValue(row);
279     return new ComplexValueIdImpl(idVal, 
280                                   ((TableImpl)_flatTable).getRowId(row));
281   }
282 
283   protected Object[] asRow(Object[] row, V value) 
284     throws IOException
285   {
286   ComplexValue.Id id = value.getId();
287     _pkCol.setRowValue(
288         row, ((id != INVALID_ID) ? id : Column.AUTO_NUMBER));
289     ComplexValueForeignKey cFk = value.getComplexValueForeignKey();
290     _complexValFkCol.setRowValue(
291         row, ((cFk != INVALID_FK) ? cFk : Column.AUTO_NUMBER));
292     return row;
293   }
294 
295   private Object[] newRowArray() {
296     Object[] row = new Object[_flatTable.getColumnCount() + 1];
297     row[row.length - 1] = ColumnImpl.RETURN_ROW_ID;
298     return row;
299   }
300   
301   @Override
302   public String toString() {
303     return CustomToStringStyle.valueBuilder(this)
304       .append("complexType", getType())
305       .append("complexTypeId", _complexTypeId)
306       .toString();
307   }
308 
309   protected static void diffFlatColumns(Table typeObjTable, 
310                                         Table flatTable,
311                                         List<Column> typeCols,
312                                         List<Column> otherCols)
313   {
314     // each "flat"" table has the columns from the "type" table, plus some
315     // others.  separate the "flat" columns into these 2 buckets
316     for(Column col : flatTable.getColumns()) {
317       if(((TableImpl)typeObjTable).hasColumn(col.getName())) {
318         typeCols.add(col);
319       } else {
320         otherCols.add(col);
321       }  
322     } 
323   }
324   
325   public abstract ComplexDataType getType();
326 
327   protected abstract V toValue(
328       ComplexValueForeignKey complexValueFk,
329       Row rawValues)
330     throws IOException;
331   
332   protected static abstract class ComplexValueImpl implements ComplexValue
333   {
334     private Id _id;
335     private ComplexValueForeignKey _complexValueFk;
336 
337     protected ComplexValueImpl(Id id, ComplexValueForeignKey complexValueFk) {
338       _id = id;
339       _complexValueFk = complexValueFk;
340     }
341 
342     public Id getId() {
343       return _id;
344     }
345 
346     public void setId(Id id) {
347       if(_id == id) {
348         // harmless, ignore
349         return;
350       }
351       if(_id != INVALID_ID) {
352         throw new IllegalStateException("id may not be reset");
353       }
354       _id = id;
355     }
356     
357     public ComplexValueForeignKey getComplexValueForeignKey() {
358       return _complexValueFk;
359     }
360 
361     public void setComplexValueForeignKey(ComplexValueForeignKey complexValueFk)
362     {
363       if(_complexValueFk == complexValueFk) {
364         // harmless, ignore
365         return;
366       }
367       if(_complexValueFk != INVALID_FK) {
368         throw new IllegalStateException("complexValueFk may not be reset");
369       }
370       _complexValueFk = complexValueFk;
371     }
372 
373     public Column getColumn() {
374       return _complexValueFk.getColumn();
375     }
376     
377     @Override
378     public int hashCode() {
379       return ((_id.get() * 37) ^ _complexValueFk.hashCode());
380     }
381 
382     @Override
383     public boolean equals(Object o) {
384       return ((this == o) ||
385               ((o != null) && (getClass() == o.getClass()) &&
386                (_id == ((ComplexValueImpl)o)._id) &&
387                _complexValueFk.equals(((ComplexValueImpl)o)._complexValueFk)));
388     }
389   }
390 
391   /**
392    * Implementation of ComplexValue.Id.
393    */
394   private static final class ComplexValueIdImpl extends ComplexValue.Id
395   {
396     private static final long serialVersionUID = 20130318L;    
397 
398     private final int _value;
399     private final RowId _rowId;
400 
401     protected ComplexValueIdImpl(int value, RowId rowId) {
402       _value = value;
403       _rowId = rowId;
404     }
405     
406     @Override
407     public int get() {
408       return _value;
409     }
410 
411     @Override
412     public RowId getRowId() {
413       return _rowId;
414     }
415   }
416   
417 }