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.time.LocalDateTime;
21  import java.util.Date;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.LinkedHashMap;
25  import java.util.Map;
26  
27  import com.healthmarketscience.jackcess.DataType;
28  import com.healthmarketscience.jackcess.PropertyMap;
29  
30  /**
31   * Map of properties for a database object.
32   *
33   * @author James Ahlborn
34   */
35  public class PropertyMapImpl implements PropertyMap
36  {
37    /** property flag bit for the DDL argument of the DAO CreateProperty
38        method.  a user cannot change or delete a DDL property without
39        dbSecWriteDef permission */
40    static final byte DDL_FLAG = (byte)0x01;
41    /** property flag bit which tells ms access to store the value without
42        running the handler bound to the property name, so that writing the
43        property records a state instead of bringing it about.  ms access sets
44        it on its own account and no DAO call produces it */
45    static final byte SKIP_HANDLER_FLAG = (byte)0x80;
46  
47    /** the type and ddl flag ms access uses for the common properties */
48    private static final Map<String,PropDef> DEFAULT_TYPES =
49      new HashMap<>();
50  
51    static {
52      // database and summary properties
53      addDefault(ACCESS_VERSION_PROP, DataType.TEXT, false);
54      addDefault(BUILD_PROP, DataType.LONG, false);
55      addDefault(PROJ_VER_PROP, DataType.INT, false);
56      addDefault(HAS_OFFLINE_LISTS_PROP, DataType.INT, false);
57      addDefault(ANSI_QUERY_MODE_PROP, DataType.LONG, true);
58      addDefault(AUTO_COMPACT_PROP, DataType.LONG, true);
59      addDefault(TRACK_NAME_AUTO_CORRECT_PROP, DataType.LONG, true);
60      addDefault(PERFORM_NAME_AUTO_CORRECT_PROP, DataType.LONG, true);
61      addDefault(CHECK_TRUNCATED_NUM_FIELDS_PROP, DataType.LONG, true);
62      addDefault(PICTURE_STORAGE_FORMAT_PROP, DataType.LONG, true);
63      addDefault(THEME_RESOURCE_NAME_PROP, DataType.TEXT, true);
64      addDefault(THEMED_FORM_CONTROLS_PROP, DataType.LONG, false);
65      addDefault(SHOW_DOCUMENT_TABS_PROP, DataType.BOOLEAN, true);
66      addDefault(USE_MDI_MODE_PROP, DataType.BYTE, true);
67      addDefault(WEB_DESIGN_MODE_PROP, DataType.BYTE, true);
68      addDefault(DESIGN_WITH_DATA_PROP, DataType.BOOLEAN, true);
69      addDefault(ALLOW_BUILT_IN_TOOLBARS_PROP, DataType.BOOLEAN, true);
70      addDefault(ALLOW_DATASHEET_SCHEMA_PROP, DataType.BOOLEAN, true);
71      addDefault(ALLOW_FULL_MENUS_PROP, DataType.BOOLEAN, true);
72      addDefault(ALLOW_SHORTCUT_MENUS_PROP, DataType.BOOLEAN, true);
73      addDefault(ALLOW_SPECIAL_KEYS_PROP, DataType.BOOLEAN, true);
74      addDefault(ALLOW_TOOLBAR_CHANGES_PROP, DataType.BOOLEAN, true);
75      addDefault(START_UP_SHOW_DB_WINDOW_PROP, DataType.BOOLEAN, true);
76      addDefault(START_UP_SHOW_STATUS_BAR_PROP, DataType.BOOLEAN, true);
77      addDefault(USE_APP_ICON_FOR_FRM_RPT_PROP, DataType.BOOLEAN, true);
78      addDefault(NAV_PANE_CATEGORY_PROP, DataType.LONG, true);
79      addDefault(NAV_PANE_CLOSED_PROP, DataType.LONG, true);
80      addDefault(NAV_PANE_SORT_BY_PROP, DataType.LONG, true);
81      addDefault(NAV_PANE_VIEW_BY_PROP, DataType.LONG, true);
82      addDefault(NAV_PANE_WIDTH_PROP, DataType.LONG, true);
83      addDefault(NAV_PANE_SEARCH_BAR_PROP, DataType.LONG, true);
84      addDefault(SHOW_VALUES_LIMIT_PROP, DataType.LONG, true);
85      addDefault(SHOW_VALUES_IN_INDEXED_PROP, DataType.LONG, true);
86      addDefault(SHOW_VALUES_IN_NON_INDEXED_PROP, DataType.LONG, true);
87      addDefault(SHOW_VALUES_IN_REMOTE_PROP, DataType.LONG, true);
88      addDefault(REPLICATE_PROJECT_PROP, DataType.BOOLEAN, false);
89      addDefault(TITLE_PROP, DataType.TEXT, false);
90      addDefault(AUTHOR_PROP, DataType.TEXT, false);
91      addDefault(COMPANY_PROP, DataType.TEXT, false);
92  
93      // column properties
94      addDefault(DEFAULT_VALUE_PROP, DataType.MEMO, true);
95      addDefault(REQUIRED_PROP, DataType.BOOLEAN, true);
96      addDefault(ALLOW_ZERO_LEN_PROP, DataType.BOOLEAN, true);
97      addDefault(DECIMAL_PLACES_PROP, DataType.BYTE, true);
98      addDefault(FORMAT_PROP, DataType.TEXT, true);
99      addDefault(INPUT_MASK_PROP, DataType.TEXT, true);
100     addDefault(CAPTION_PROP, DataType.MEMO, true);
101     addDefault(VALIDATION_RULE_PROP, DataType.TEXT, true);
102     addDefault(VALIDATION_TEXT_PROP, DataType.TEXT, true);
103     addDefault(GUID_PROP, DataType.BINARY, true);
104     addDefault(DESCRIPTION_PROP, DataType.TEXT, true);
105     addDefault(RESULT_TYPE_PROP, DataType.BYTE, true);
106     addDefault(EXPRESSION_PROP, DataType.MEMO, true);
107     addDefault(ALLOW_MULTI_VALUE_PROP, DataType.BOOLEAN, true);
108     addDefault(ROW_SOURCE_TYPE_PROP, DataType.TEXT, true);
109     addDefault(ROW_SOURCE_PROP, DataType.MEMO, true);
110     addDefault(BOUND_COLUMN_PROP, DataType.INT, true);
111     addDefault(COLUMN_COUNT_PROP, DataType.INT, true);
112     addDefault(COLUMN_WIDTHS_PROP, DataType.TEXT, true);
113     addDefault(LIMIT_TO_LIST_PROP, DataType.BOOLEAN, true);
114     addDefault(DISPLAY_CONTROL_PROP, DataType.INT, true);
115     addDefault(TEXT_FORMAT_PROP, DataType.BYTE, true);
116     addDefault(IME_MODE_PROP, DataType.BYTE, true);
117     addDefault(IME_SENTENCE_MODE_PROP, DataType.BYTE, true);
118     addDefault(UNICODE_COMPRESSION_PROP, DataType.BOOLEAN, true);
119     addDefault(APPEND_ONLY_PROP, DataType.BOOLEAN, true);
120     addDefault(TEXT_ALIGN_PROP, DataType.BYTE, false);
121     addDefault(AGGREGATE_TYPE_PROP, DataType.LONG, false);
122     addDefault(CURRENCY_LCID_PROP, DataType.LONG, false);
123     addDefault(COLUMN_HIDDEN_PROP, DataType.BOOLEAN, false);
124     addDefault(COLUMN_ORDER_PROP, DataType.INT, false);
125     addDefault(COLUMN_WIDTH_PROP, DataType.INT, false);
126 
127     // table and query properties
128     addDefault(NAME_MAP_PROP, DataType.OLE, false);
129     addDefault(DEFAULT_VIEW_PROP, DataType.BYTE, false);
130     addDefault(ORIENTATION_PROP, DataType.BYTE, true);
131     addDefault(ORDER_BY_ON_PROP, DataType.BOOLEAN, false);
132     addDefault(ORDER_BY_ON_LOAD_PROP, DataType.BOOLEAN, false);
133     addDefault(FILTER_ON_LOAD_PROP, DataType.BOOLEAN, false);
134     addDefault(TOTALS_ROW_PROP, DataType.BOOLEAN, false);
135     addDefault(HIDE_NEW_FIELD_PROP, DataType.BOOLEAN, false);
136     addDefault(PUBLISH_TO_WEB_PROP, DataType.BYTE, false);
137     addDefault(READ_ONLY_WHEN_DISCONNECTED_PROP, DataType.BOOLEAN, false);
138     addDefault(DISPLAY_VIEWS_ON_SHAREPOINT_PROP, DataType.BYTE, false);
139     addDefault(BACK_TINT_PROP, DataType.FLOAT, false);
140     addDefault(BACK_SHADE_PROP, DataType.FLOAT, false);
141     addDefault(ALTERNATE_BACK_TINT_PROP, DataType.FLOAT, false);
142     addDefault(ALTERNATE_BACK_SHADE_PROP, DataType.FLOAT, false);
143     addDefault(ALTERNATE_BACK_THEME_COLOR_INDEX_PROP, DataType.LONG, false);
144     addDefault(DATASHEET_FORE_THEME_COLOR_INDEX_PROP, DataType.LONG, false);
145     addDefault(DATASHEET_GRIDLINES_THEME_COLOR_INDEX_PROP, DataType.LONG, false);
146     addDefault(THEME_FONT_INDEX_PROP, DataType.LONG, false);
147     addDefault(QUERY_NAME_MAP_PROP, DataType.OLE, false);
148     addDefault(ODBC_TIMEOUT_PROP, DataType.INT, true);
149     addDefault(RECORD_LOCKS_PROP, DataType.BYTE, true);
150     addDefault(RECORDSET_TYPE_PROP, DataType.BYTE, true);
151     addDefault(USE_TRANSACTION_PROP, DataType.BOOLEAN, true);
152     addDefault(FAIL_ON_ERROR_PROP, DataType.BOOLEAN, true);
153     addDefault(LOG_MESSAGES_PROP, DataType.BOOLEAN, true);
154   }
155 
156   private static void addDefault(String name, DataType type, boolean isDdl) {
157     DEFAULT_TYPES.put(name, new PropDef(type, isDdl));
158   }
159 
160   private final String _mapName;
161   private final short _mapType;
162   private final Map<String,Property> _props =
163     new LinkedHashMap<>();
164   private final PropertyMaps _owner;
165 
166   public PropertyMapImpl(String name, short type, PropertyMaps owner) {
167     _mapName = name;
168     _mapType = type;
169     _owner = owner;
170   }
171 
172   @Override
173   public String getName() {
174     return _mapName;
175   }
176 
177   public short getType() {
178     return _mapType;
179   }
180 
181   public PropertyMaps getOwner() {
182     return _owner;
183   }
184 
185   @Override
186   public int getSize() {
187     return _props.size();
188   }
189 
190   @Override
191   public boolean isEmpty() {
192     return _props.isEmpty();
193   }
194 
195   @Override
196   public Property get(String name) {
197     return _props.get(DatabaseImpl.toLookupName(name));
198   }
199 
200   @Override
201   public Object getValue(String name) {
202     return getValue(name, null);
203   }
204 
205   @Override
206   public Object getValue(String name, Object defaultValue) {
207     Property prop = get(name);
208     Object value = defaultValue;
209     if((prop != null) && (prop.getValue() != null)) {
210       value = prop.getValue();
211     }
212     return value;
213   }
214 
215   @Override
216   public PropertyImpl put(String name, Object value) {
217     return put(name, null, value, false);
218   }
219 
220   @Override
221   public PropertyImpl put(String name, DataType type, Object value) {
222     return put(name, type, value, false);
223   }
224 
225   @Override
226   public void putAll(Iterable<? extends Property> props) {
227     if(props == null) {
228       return;
229     }
230 
231     for(Property prop : props) {
232       put(prop);
233     }
234   }
235 
236   public PropertyImpl put(Property prop) {
237     if(prop instanceof PropertyImpl) {
238       // keep the whole flag byte, not just the ddl bit, and do not add the
239       // default ddl flag to a property which does not have it
240       PropertyImpl propImpl = (PropertyImpl)prop;
241       return putStored(propImpl.getName(), propImpl.getType(),
242                        propImpl.getValue(), propImpl.getFlags());
243     }
244     return put(prop.getName(), prop.getType(), prop.getValue(),
245                toFlags(prop.isDdl(), prop.isSkipHandler()));
246   }
247 
248   /**
249    * Puts a property into this map with the given information.
250    */
251   @Override
252   public PropertyImpl put(String name, DataType type, Object value,
253                           boolean isDdl) {
254     return put(name, type, value, toFlags(isDdl, false));
255   }
256 
257   @Override
258   public PropertyImpl put(String name, DataType type, Object value,
259                           boolean isDdl, boolean isSkipHandler) {
260     return put(name, type, value, toFlags(isDdl, isSkipHandler));
261   }
262 
263   /**
264    * Puts a property into this map with the given flag byte, to which the
265    * default ddl flag of a common property is added.
266    */
267   PropertyImpl put(String name, DataType type, Object value, byte flags) {
268     PropertyImpl prop = (PropertyImpl)createProperty(name, type, value, flags);
269     _props.put(DatabaseImpl.toLookupName(name), prop);
270     return prop;
271   }
272 
273   /**
274    * Puts a property into this map with the type and flag byte exactly as they
275    * are stored in the file.
276    */
277   PropertyImpl putStored(String name, DataType type, Object value,
278                          byte flags) {
279     PropertyImpl prop = new PropertyImpl(name, type, value, flags);
280     _props.put(DatabaseImpl.toLookupName(name), prop);
281     return prop;
282   }
283 
284   @Override
285   public PropertyImpl remove(String name) {
286     return (PropertyImpl)_props.remove(DatabaseImpl.toLookupName(name));
287   }
288 
289   @Override
290   public Iterator<Property> iterator() {
291     return _props.values().iterator();
292   }
293 
294   @Override
295   public void save() throws IOException {
296     getOwner().save();
297   }
298 
299   @Override
300   public String toString() {
301     return toString(this);
302   }
303 
304   public static String toString(PropertyMap map) {
305     StringBuilder sb = new StringBuilder();
306     sb.append(PropertyMaps.DEFAULT_NAME.equals(map.getName()) ?
307               "<DEFAULT>" : map.getName())
308       .append(" {");
309     for(Iterator<Property> iter = map.iterator(); iter.hasNext(); ) {
310       sb.append(iter.next());
311       if(iter.hasNext()) {
312         sb.append(",");
313       }
314     }
315     sb.append("}");
316     return sb.toString();
317   }
318 
319   public static Property createProperty(String name, DataType type, Object value) {
320     return createProperty(name, type, value, false);
321   }
322 
323   public static Property createProperty(String name, DataType type,
324                                         Object value, boolean isDdl) {
325     return createProperty(name, type, value, toFlags(isDdl, false));
326   }
327 
328   public static Property createProperty(String name, DataType type,
329                                         Object value, boolean isDdl,
330                                         boolean isSkipHandler) {
331     return createProperty(name, type, value, toFlags(isDdl, isSkipHandler));
332   }
333 
334   static Property createProperty(String name, DataType type,
335                                  Object value, byte flags) {
336     // see if this is a builtin property that we already understand
337     PropDef pd = DEFAULT_TYPES.get(name);
338 
339     if(value instanceof PropertyMap.EnumValue) {
340       // convert custom enum to stored value
341       value = ((PropertyMap.EnumValue)value).getValue();
342     }
343 
344     if(pd != null) {
345       // update according to the default info
346       type = ((type == null) ? pd._type : type);
347       if(pd._isDdl) {
348         flags |= DDL_FLAG;
349       }
350     } else if(type == null) {
351       // choose the type based on the value
352       if(value instanceof String) {
353         type = DataType.TEXT;
354       } else if(value instanceof Boolean) {
355         type = DataType.BOOLEAN;
356       } else if(value instanceof Byte) {
357         type = DataType.BYTE;
358       } else if(value instanceof Short) {
359         type = DataType.INT;
360       } else if(value instanceof Integer) {
361         type = DataType.LONG;
362       } else if(value instanceof Float) {
363         type = DataType.FLOAT;
364       } else if(value instanceof Double) {
365         type = DataType.DOUBLE;
366       } else if((value instanceof Date) || (value instanceof LocalDateTime)) {
367         type = DataType.SHORT_DATE_TIME;
368       } else if(value instanceof byte[]) {
369         type = DataType.OLE;
370       } else if(value instanceof Long) {
371         type = DataType.BIG_INT;
372       } else {
373         throw new IllegalArgumentException(
374             "Could not determine type for property " + name +
375             " with value " + value);
376       }
377     }
378 
379     return new PropertyImpl(name, type, value, flags);
380   }
381 
382   private static byte toFlags(boolean isDdl, boolean isSkipHandler) {
383     return (byte)((isDdl ? DDL_FLAG : 0) |
384                   (isSkipHandler ? SKIP_HANDLER_FLAG : 0));
385   }
386 
387   /**
388    * Info about a property defined in a PropertyMap.
389    */
390   static final class PropertyImpl implements PropertyMap.Property
391   {
392     private final String _name;
393     private final DataType _type;
394     /** the flag byte exactly as it is stored in the file */
395     private final byte _flags;
396     private Object _value;
397 
398     private PropertyImpl(String name, DataType type, Object value,
399                          byte flags) {
400       _name = name;
401       _type = type;
402       _flags = flags;
403       _value = value;
404     }
405 
406     @Override
407     public String getName() {
408       return _name;
409     }
410 
411     @Override
412     public DataType getType() {
413       return _type;
414     }
415 
416     @Override
417     public Object getValue() {
418       return _value;
419     }
420 
421     @Override
422     public void setValue(Object newValue) {
423       _value = newValue;
424     }
425 
426     @Override
427     public boolean isDdl() {
428       return ((_flags & DDL_FLAG) != 0);
429     }
430 
431     @Override
432     public boolean isSkipHandler() {
433       return ((_flags & SKIP_HANDLER_FLAG) != 0);
434     }
435 
436     /**
437      * @return the flag byte as stored in the file.  Access uses more of it
438      *         than the one bit {@link #isDdl} reports, so the whole byte has
439      *         to be written back unchanged.
440      */
441     byte getFlags() {
442       return _flags;
443     }
444 
445     @Override
446     public String toString() {
447       Object val = getValue();
448       if(val instanceof byte[]) {
449         val = ByteUtil.toHexString((byte[])val);
450       }
451       String flagStr = (isDdl() ? ":ddl" : "") +
452         (isSkipHandler() ? ":nohandler" : "");
453       return getName() + "[" + getType() + flagStr + "]=" + val;
454     }
455   }
456 
457   /**
458    * Helper for holding info about default properties
459    */
460   private static final class PropDef
461   {
462     private final DataType _type;
463     private final boolean _isDdl;
464 
465     private PropDef(DataType type, boolean isDdl) {
466       _type = type;
467       _isDdl = isDdl;
468     }
469   }
470 }