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  
58  
59    /**
60     * Creates a ComplexColumnInfo for a complex column.
61     */
62    public static ComplexColumnInfo<? extends ComplexValue> create(
63        ColumnImpl column, ByteBuffer buffer, int offset)
64      throws IOException
65    {
66      int complexTypeId = buffer.getInt(
67          offset + column.getFormat().OFFSET_COLUMN_COMPLEX_ID);
68  
69      DatabaseImpl db = column.getDatabase();
70      TableImpl complexColumns = db.getSystemComplexColumns();
71      IndexCursor cursor = CursorBuilder.createCursor(
72          complexColumns.getPrimaryKeyIndex());
73      if(!cursor.findFirstRowByEntry(complexTypeId)) {
74        throw new IOException(column.withErrorContext(
75            "Could not find complex column info for complex column with id " +
76            complexTypeId));
77      }
78      Row cColRow = cursor.getCurrentRow();
79      int tableId = cColRow.getInt(COL_TABLE_ID);
80      if(tableId != column.getTable().getTableDefPageNumber()) {
81        throw new IOException(column.withErrorContext(
82            "Found complex column for table " + tableId + " but expected table " +
83            column.getTable().getTableDefPageNumber()));
84      }
85      int flatTableId = cColRow.getInt(COL_FLAT_TABLE_ID);
86      int typeObjId = cColRow.getInt(COL_COMPLEX_TYPE_OBJECT_ID);
87  
88      TableImpl typeObjTable = db.getTable(typeObjId);
89      TableImpl flatTable = db.getTable(flatTableId);
90  
91      if((typeObjTable == null) || (flatTable == null)) {
92        throw new IOException(column.withErrorContext(
93            "Could not find supporting tables (" + typeObjId + ", " + flatTableId
94            + ") for complex column with id " + complexTypeId));
95      }
96      
97      // we inspect the structore of the "type table" to determine what kind of
98      // complex info we are dealing with
99      if(isMultiValueColumn(typeObjTable)) {
100       return new MultiValueColumnInfoImpl(column, complexTypeId, typeObjTable,
101                                       flatTable);
102     } else if(isAttachmentColumn(typeObjTable)) {
103       return new AttachmentColumnInfoImpl(column, complexTypeId, typeObjTable,
104                                       flatTable);
105     } else if(isVersionHistoryColumn(typeObjTable)) {
106       return new VersionHistoryColumnInfoImpl(column, complexTypeId, typeObjTable,
107                                           flatTable);
108     }
109     
110     LOG.warn(column.withErrorContext(
111                  "Unsupported complex column type " + typeObjTable.getName()));
112     return new UnsupportedColumnInfoImpl(column, complexTypeId, typeObjTable,
113                                          flatTable);
114   }
115 
116 
117   public static boolean isMultiValueColumn(Table typeObjTable) {
118     // if we found a single value of a "simple" type, then we are dealing with
119     // a multi-value column
120     List<? extends Column> typeCols = typeObjTable.getColumns();
121     return ((typeCols.size() == 1) &&
122             MULTI_VALUE_TYPES.contains(typeCols.get(0).getType()));
123   }
124 
125   public static boolean isAttachmentColumn(Table typeObjTable) {
126     // attachment data has these columns FileURL(MEMO), FileName(TEXT),
127     // FileType(TEXT), FileData(OLE), FileTimeStamp(SHORT_DATE_TIME),
128     // FileFlags(LONG)
129     List<? extends Column> typeCols = typeObjTable.getColumns();
130     if(typeCols.size() < 6) {
131       return false;
132     }
133 
134     int numMemo = 0;
135     int numText = 0;
136     int numDate = 0;
137     int numOle= 0;
138     int numLong = 0;
139     
140     for(Column col : typeCols) {
141       switch(col.getType()) {
142       case TEXT:
143         ++numText;
144         break;
145       case LONG:
146         ++numLong;
147         break;
148       case SHORT_DATE_TIME:
149         ++numDate;
150         break;
151       case OLE:
152         ++numOle;
153         break;
154       case MEMO:
155         ++numMemo;
156         break;
157       default:
158         // ignore
159       }
160     }
161 
162     // be flexible, allow for extra columns...
163     return((numMemo >= 1) && (numText >= 2) && (numOle >= 1) &&
164            (numDate >= 1) && (numLong >= 1));
165   }
166 
167   public static boolean isVersionHistoryColumn(Table typeObjTable) {
168     // version history data has these columns <value>(MEMO),
169     // <modified>(SHORT_DATE_TIME)
170     List<? extends Column> typeCols = typeObjTable.getColumns();
171     if(typeCols.size() < 2) {
172       return false;
173     }
174 
175     int numMemo = 0;
176     int numDate = 0;
177     
178     for(Column col : typeCols) {
179       switch(col.getType()) {
180       case SHORT_DATE_TIME:
181         ++numDate;
182         break;
183       case MEMO:
184         ++numMemo;
185         break;
186       default:
187         // ignore
188       }
189     }
190 
191     // be flexible, allow for extra columns...
192     return((numMemo >= 1) && (numDate >= 1));
193   }
194 }