View Javadoc
1   /*
2   Copyright (c) 2026 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.EnumSet;
21  import java.util.Set;
22  
23  import com.healthmarketscience.jackcess.Column;
24  import com.healthmarketscience.jackcess.DataType;
25  import com.healthmarketscience.jackcess.PropertyMap;
26  import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
27  import com.healthmarketscience.jackcess.complex.ComplexDataType;
28  import com.healthmarketscience.jackcess.impl.complex.MultiValueColumnInfoImpl;
29  
30  /**
31   * The complex column which a {@link com.healthmarketscience.jackcess.ColumnBuilder}
32   * declares.  A complex column is a column plus a table of values and a row
33   * which ties the two together, and this carries what the caller asked for
34   * until {@link ComplexColumnCreator} builds them.
35   *
36   * @author James Ahlborn
37   * @usage _advanced_class_
38   */
39  public class ComplexColumnDesc
40  {
41    /** the value types which have a shipped type table */
42    private static final Set<DataType> VALUE_TYPES = EnumSet.of(
43        DataType.BYTE, DataType.INT, DataType.LONG, DataType.FLOAT,
44        DataType.DOUBLE, DataType.GUID, DataType.NUMERIC, DataType.TEXT);
45  
46    /** the kind of complex column */
47    private final ComplexDataType _type;
48    /** the type of a multi-value column's values */
49    private final DataType _valueType;
50    /** the name of the memo column a version history column tracks */
51    private final String _memoColumnName;
52    /** the RowSourceType property of a multi-value column */
53    private String _rowSourceType;
54    /** the RowSource property of a multi-value column */
55    private String _rowSource;
56    /** the number of columns the row source gives */
57    private int _columnCount = 1;
58  
59    private ComplexColumnDesc(ComplexDataType type, DataType valueType,
60                              String memoColumnName) {
61      _type = type;
62      _valueType = valueType;
63      _memoColumnName = memoColumnName;
64    }
65  
66    public static ComplexColumnDesc multiValue(DataType valueType) {
67      return new ComplexColumnDesc(ComplexDataType.MULTI_VALUE, valueType, null);
68    }
69  
70    public static ComplexColumnDesc attachment() {
71      return new ComplexColumnDesc(ComplexDataType.ATTACHMENT, null, null);
72    }
73  
74    public static ComplexColumnDesc versionHistory(String memoColumnName) {
75      return new ComplexColumnDesc(ComplexDataType.VERSION_HISTORY, null,
76                                   memoColumnName);
77    }
78  
79    /**
80     * Reads the declaration back off a complex column which exists, so that a
81     * column can be copied with {@link
82     * com.healthmarketscience.jackcess.ColumnBuilder#setFromColumn}.
83     *
84     * @return the declaration, or {@code null} if the column is not a complex one
85     */
86    public static ComplexColumnDesc fromColumn(Column template)
87      throws IOException
88    {
89      ComplexColumnInfo<?> info = template.getComplexInfo();
90      if(info == null) {
91        return null;
92      }
93  
94      switch(info.getType()) {
95      case ATTACHMENT:
96        return attachment();
97      case MULTI_VALUE:
98        Column valueCol = ((MultiValueColumnInfoImpl)info).getValueColumn();
99        ComplexColumnDesc desc = multiValue(valueCol.getType());
100       PropertyMap props = valueCol.getProperties();
101       desc.setRowSource(
102           (String)props.getValue(PropertyMap.ROW_SOURCE_TYPE_PROP),
103           (String)props.getValue(PropertyMap.ROW_SOURCE_PROP),
104           toColumnCount(props.getValue(PropertyMap.COLUMN_COUNT_PROP)));
105       return desc;
106     case VERSION_HISTORY:
107       throw noVersionHistoryCopy("Column=" + template.getName());
108     default:
109       throw new IllegalArgumentException(
110           "Cannot copy a complex column of a kind jackcess cannot read " +
111           "(Column=" + template.getName() + ")");
112     }
113   }
114 
115   private static int toColumnCount(Object value) {
116     return ((value instanceof Number) ? ((Number)value).intValue() : 1);
117   }
118 
119   /**
120    * @return a declaration of the same complex column
121    */
122   public ComplexColumnDesc copy() {
123     if(_type == ComplexDataType.VERSION_HISTORY) {
124       // a copy would follow the memo column of the original, under whatever
125       // name the copy is given, which is a column ms access will not open
126       throw noVersionHistoryCopy("MemoColumn=" + _memoColumnName);
127     }
128     ComplexColumnDesc/ComplexColumnDesc.html#ComplexColumnDesc">ComplexColumnDesc desc = new ComplexColumnDesc(_type, _valueType,
129                                                    _memoColumnName);
130     desc.setRowSource(_rowSourceType, _rowSource, _columnCount);
131     return desc;
132   }
133 
134   private static IllegalArgumentException noVersionHistoryCopy(String what) {
135     return new IllegalArgumentException(
136         "A version history column follows the memo column it holds the " +
137         "versions of, so it is copied with setAppendOnly on that column " +
138         "rather than on its own (" + what + ")");
139   }
140 
141   public ComplexDataType getType() {
142     return _type;
143   }
144 
145   public DataType getValueType() {
146     return _valueType;
147   }
148 
149   public String getMemoColumnName() {
150     return _memoColumnName;
151   }
152 
153   public String getRowSourceType() {
154     return _rowSourceType;
155   }
156 
157   public String getRowSource() {
158     return _rowSource;
159   }
160 
161   public int getColumnCount() {
162     return _columnCount;
163   }
164 
165   public void setRowSource(String rowSourceType, String rowSource,
166                            int columnCount) {
167     _rowSourceType = rowSourceType;
168     _rowSource = rowSource;
169     _columnCount = columnCount;
170   }
171 
172   /**
173    * Checks that this declaration is complete enough to build.
174    *
175    * @throws IllegalArgumentException if it is not
176    */
177   public void validate(String colName) {
178     if(_type != ComplexDataType.MULTI_VALUE) {
179       return;
180     }
181     if(!VALUE_TYPES.contains(_valueType)) {
182       throw new IllegalArgumentException(
183           "A multi-value column holds values of type " + VALUE_TYPES +
184           ", not " + _valueType + " (Column=" + colName + ")");
185     }
186     // ms access shows the values a column holds whatever its row source is,
187     // but offers no way to add one without a row source which names at least
188     // one value
189     if((_rowSource == null) || _rowSource.isEmpty()) {
190       throw new IllegalArgumentException(
191           "A multi-value column needs a row source, from either setValueList " +
192           "or setRowSource (Column=" + colName + ")");
193     }
194   }
195 
196   @Override
197   public String toString() {
198     return ToStringBuilder.builder(this)
199       .append("type", _type)
200       .appendIfNotNull("valueType", _valueType)
201       .appendIfNotNull("memoColumn", _memoColumnName)
202       .appendIfNotNull("rowSourceType", _rowSourceType)
203       .appendIfNotNull("rowSource", _rowSource)
204       .toString();
205   }
206 }