View Javadoc
1   /*
2   Copyright (c) 2013 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.nio.ByteBuffer;
21  import java.util.EnumSet;
22  import java.util.List;
23  import java.util.Set;
24  
25  import com.healthmarketscience.jackcess.Column;
26  import com.healthmarketscience.jackcess.CursorBuilder;
27  import com.healthmarketscience.jackcess.DataType;
28  import com.healthmarketscience.jackcess.IndexCursor;
29  import com.healthmarketscience.jackcess.Row;
30  import com.healthmarketscience.jackcess.Table;
31  import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
32  import com.healthmarketscience.jackcess.complex.ComplexValue;
33  import com.healthmarketscience.jackcess.impl.complex.AttachmentColumnInfoImpl;
34  import com.healthmarketscience.jackcess.impl.complex.MultiValueColumnInfoImpl;
35  import com.healthmarketscience.jackcess.impl.complex.UnsupportedColumnInfoImpl;
36  import com.healthmarketscience.jackcess.impl.complex.VersionHistoryColumnInfoImpl;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  
40  
41  /**
42   * Utility code for loading complex columns.
43   *
44   * @author James Ahlborn
45   */
46  public class ComplexColumnSupport 
47  {
48    private static final Log LOG = LogFactory.getLog(ComplexColumnSupport.class);
49  
50    private static final String COL_COMPLEX_TYPE_OBJECT_ID = "ComplexTypeObjectID";
51    private static final String COL_TABLE_ID = "ConceptualTableID";
52    private static final String COL_FLAT_TABLE_ID = "FlatTableID";
53  
54    private static final Set<DataType> MULTI_VALUE_TYPES = EnumSet.of(
55        DataType.BYTE, DataType.INT, DataType.LONG, DataType.FLOAT,
56        DataType.DOUBLE, DataType.GUID, DataType.NUMERIC, DataType.TEXT,
57        DataType.BIG_INT);
58  
59  
60    /**
61     * Creates a ComplexColumnInfo for a complex column.
62     */
63    public static ComplexColumnInfo<? extends ComplexValue> create(
64        ColumnImpl column, ByteBuffer buffer, int offset)
65      throws IOException
66    {
67      int complexTypeId = buffer.getInt(
68          offset + column.getFormat().OFFSET_COLUMN_COMPLEX_ID);
69  
70      DatabaseImpl db = column.getDatabase();
71      TableImpl complexColumns = db.getSystemComplexColumns();
72      IndexCursor cursor = CursorBuilder.createCursor(
73          complexColumns.getPrimaryKeyIndex());
74      if(!cursor.findFirstRowByEntry(complexTypeId)) {
75        throw new IOException(column.withErrorContext(
76            "Could not find complex column info for complex column with id " +
77            complexTypeId));
78      }
79      Row cColRow = cursor.getCurrentRow();
80      int tableId = cColRow.getInt(COL_TABLE_ID);
81      if(tableId != column.getTable().getTableDefPageNumber()) {
82        throw new IOException(column.withErrorContext(
83            "Found complex column for table " + tableId + " but expected table " +
84            column.getTable().getTableDefPageNumber()));
85      }
86      int flatTableId = cColRow.getInt(COL_FLAT_TABLE_ID);
87      int typeObjId = cColRow.getInt(COL_COMPLEX_TYPE_OBJECT_ID);
88  
89      TableImpl typeObjTable = db.getTable(typeObjId);
90      TableImpl flatTable = db.getTable(flatTableId);
91  
92      if((typeObjTable == null) || (flatTable == null)) {
93        throw new IOException(column.withErrorContext(
94            "Could not find supporting tables (" + typeObjId + ", " + flatTableId
95            + ") for complex column with id " + complexTypeId));
96      }
97      
98      // we inspect the structore of the "type table" to determine what kind of
99      // complex info we are dealing with
100     if(isMultiValueColumn(typeObjTable)) {
101       return new MultiValueColumnInfoImpl(column, complexTypeId, typeObjTable,
102                                       flatTable);
103     } else if(isAttachmentColumn(typeObjTable)) {
104       return new AttachmentColumnInfoImpl(column, complexTypeId, typeObjTable,
105                                       flatTable);
106     } else if(isVersionHistoryColumn(typeObjTable)) {
107       return new VersionHistoryColumnInfoImpl(column, complexTypeId, typeObjTable,
108                                           flatTable);
109     }
110     
111     LOG.warn(column.withErrorContext(
112                  "Unsupported complex column type " + typeObjTable.getName()));
113     return new UnsupportedColumnInfoImpl(column, complexTypeId, typeObjTable,
114                                          flatTable);
115   }
116 
117 
118   public static boolean isMultiValueColumn(Table typeObjTable) {
119     // if we found a single value of a "simple" type, then we are dealing with
120     // a multi-value column
121     List<? extends Column> typeCols = typeObjTable.getColumns();
122     return ((typeCols.size() == 1) &&
123             MULTI_VALUE_TYPES.contains(typeCols.get(0).getType()));
124   }
125 
126   public static boolean isAttachmentColumn(Table typeObjTable) {
127     // attachment data has these columns FileURL(MEMO), FileName(TEXT),
128     // FileType(TEXT), FileData(OLE), FileTimeStamp(SHORT_DATE_TIME),
129     // FileFlags(LONG)
130     List<? extends Column> typeCols = typeObjTable.getColumns();
131     if(typeCols.size() < 6) {
132       return false;
133     }
134 
135     int numMemo = 0;
136     int numText = 0;
137     int numDate = 0;
138     int numOle= 0;
139     int numLong = 0;
140     
141     for(Column col : typeCols) {
142       switch(col.getType()) {
143       case TEXT:
144         ++numText;
145         break;
146       case LONG:
147         ++numLong;
148         break;
149       case SHORT_DATE_TIME:
150         ++numDate;
151         break;
152       case OLE:
153         ++numOle;
154         break;
155       case MEMO:
156         ++numMemo;
157         break;
158       default:
159         // ignore
160       }
161     }
162 
163     // be flexible, allow for extra columns...
164     return((numMemo >= 1) && (numText >= 2) && (numOle >= 1) &&
165            (numDate >= 1) && (numLong >= 1));
166   }
167 
168   public static boolean isVersionHistoryColumn(Table typeObjTable) {
169     // version history data has these columns <value>(MEMO),
170     // <modified>(SHORT_DATE_TIME)
171     List<? extends Column> typeCols = typeObjTable.getColumns();
172     if(typeCols.size() < 2) {
173       return false;
174     }
175 
176     int numMemo = 0;
177     int numDate = 0;
178     
179     for(Column col : typeCols) {
180       switch(col.getType()) {
181       case SHORT_DATE_TIME:
182         ++numDate;
183         break;
184       case MEMO:
185         ++numMemo;
186         break;
187       default:
188         // ignore
189       }
190     }
191 
192     // be flexible, allow for extra columns...
193     return((numMemo >= 1) && (numDate >= 1));
194   }
195 }