You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PropertyMaps.java 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /*
  2. Copyright (c) 2011 James Ahlborn
  3. This library is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public
  5. License as published by the Free Software Foundation; either
  6. version 2.1 of the License, or (at your option) any later version.
  7. This library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public
  12. License along with this library; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  14. USA
  15. */
  16. package com.healthmarketscience.jackcess;
  17. import java.io.IOException;
  18. import java.nio.ByteBuffer;
  19. import java.util.ArrayList;
  20. import java.util.HashMap;
  21. import java.util.Iterator;
  22. import java.util.LinkedHashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. /**
  26. * Collection of PropertyMap instances read from a single property data block.
  27. *
  28. * @author James Ahlborn
  29. */
  30. public class PropertyMaps implements Iterable<PropertyMap>
  31. {
  32. /** the name of the "default" properties for a PropertyMaps instance */
  33. public static final String DEFAULT_NAME = "";
  34. private static final short PROPERTY_NAME_LIST = 0x80;
  35. private static final short DEFAULT_PROPERTY_VALUE_LIST = 0x00;
  36. private static final short COLUMN_PROPERTY_VALUE_LIST = 0x01;
  37. /** maps the PropertyMap name (case-insensitive) to the PropertyMap
  38. instance */
  39. private final Map<String,PropertyMap> _maps =
  40. new LinkedHashMap<String,PropertyMap>();
  41. private final int _objectId;
  42. public PropertyMaps(int objectId) {
  43. _objectId = objectId;
  44. }
  45. public int getObjectId() {
  46. return _objectId;
  47. }
  48. public int getSize() {
  49. return _maps.size();
  50. }
  51. public boolean isEmpty() {
  52. return _maps.isEmpty();
  53. }
  54. /**
  55. * @return the unnamed "default" PropertyMap in this group, creating if
  56. * necessary.
  57. */
  58. public PropertyMap getDefault() {
  59. return get(DEFAULT_NAME, DEFAULT_PROPERTY_VALUE_LIST);
  60. }
  61. /**
  62. * @return the PropertyMap with the given name in this group, creating if
  63. * necessary
  64. */
  65. public PropertyMap get(String name) {
  66. return get(name, COLUMN_PROPERTY_VALUE_LIST);
  67. }
  68. /**
  69. * @return the PropertyMap with the given name and type in this group,
  70. * creating if necessary
  71. */
  72. private PropertyMap get(String name, short type) {
  73. String lookupName = Database.toLookupName(name);
  74. PropertyMap map = _maps.get(lookupName);
  75. if(map == null) {
  76. map = new PropertyMap(name, type);
  77. _maps.put(lookupName, map);
  78. }
  79. return map;
  80. }
  81. /**
  82. * Adds the given PropertyMap to this group.
  83. */
  84. public void put(PropertyMap map) {
  85. _maps.put(Database.toLookupName(map.getName()), map);
  86. }
  87. public Iterator<PropertyMap> iterator() {
  88. return _maps.values().iterator();
  89. }
  90. @Override
  91. public String toString() {
  92. StringBuilder sb = new StringBuilder();
  93. for(Iterator<PropertyMap> iter = iterator(); iter.hasNext(); ) {
  94. sb.append(iter.next());
  95. if(iter.hasNext()) {
  96. sb.append("\n");
  97. }
  98. }
  99. return sb.toString();
  100. }
  101. /**
  102. * Utility class for reading/writing property blocks.
  103. */
  104. static final class Handler
  105. {
  106. /** the current database */
  107. private final Database _database;
  108. /** cache of PropColumns used to read/write property values */
  109. private final Map<DataType,PropColumn> _columns =
  110. new HashMap<DataType,PropColumn>();
  111. Handler(Database database) {
  112. _database = database;
  113. }
  114. /**
  115. * @return a PropertyMaps instance decoded from the given bytes (always
  116. * returns non-{@code null} result).
  117. */
  118. public PropertyMaps read(byte[] propBytes, int objectId)
  119. throws IOException
  120. {
  121. PropertyMaps maps = new PropertyMaps(objectId);
  122. if((propBytes == null) || (propBytes.length == 0)) {
  123. return maps;
  124. }
  125. ByteBuffer bb = ByteBuffer.wrap(propBytes)
  126. .order(PageChannel.DEFAULT_BYTE_ORDER);
  127. // check for known header
  128. boolean knownType = false;
  129. for(byte[] tmpType : JetFormat.PROPERTY_MAP_TYPES) {
  130. if(ByteUtil.matchesRange(bb, bb.position(), tmpType)) {
  131. ByteUtil.forward(bb, tmpType.length);
  132. knownType = true;
  133. break;
  134. }
  135. }
  136. if(!knownType) {
  137. throw new IOException("Unknown property map type " +
  138. ByteUtil.toHexString(bb, 4));
  139. }
  140. // parse each data "chunk"
  141. List<String> propNames = null;
  142. while(bb.hasRemaining()) {
  143. int len = bb.getInt();
  144. short type = bb.getShort();
  145. int endPos = bb.position() + len - 6;
  146. ByteBuffer bbBlock = PageChannel.narrowBuffer(bb, bb.position(),
  147. endPos);
  148. if(type == PROPERTY_NAME_LIST) {
  149. propNames = readPropertyNames(bbBlock);
  150. } else if((type == DEFAULT_PROPERTY_VALUE_LIST) ||
  151. (type == COLUMN_PROPERTY_VALUE_LIST)) {
  152. maps.put(readPropertyValues(bbBlock, propNames, type));
  153. } else {
  154. throw new IOException("Unknown property block type " + type);
  155. }
  156. bb.position(endPos);
  157. }
  158. return maps;
  159. }
  160. /**
  161. * @return the property names parsed from the given data chunk
  162. */
  163. private List<String> readPropertyNames(ByteBuffer bbBlock) {
  164. List<String> names = new ArrayList<String>();
  165. while(bbBlock.hasRemaining()) {
  166. names.add(readPropName(bbBlock));
  167. }
  168. return names;
  169. }
  170. /**
  171. * @return the PropertyMap created from the values parsed from the given
  172. * data chunk combined with the given property names
  173. */
  174. private PropertyMap readPropertyValues(
  175. ByteBuffer bbBlock, List<String> propNames, short blockType)
  176. throws IOException
  177. {
  178. String mapName = DEFAULT_NAME;
  179. if(bbBlock.hasRemaining()) {
  180. // read the map name, if any
  181. int nameBlockLen = bbBlock.getInt();
  182. int endPos = bbBlock.position() + nameBlockLen - 4;
  183. if(nameBlockLen > 6) {
  184. mapName = readPropName(bbBlock);
  185. }
  186. bbBlock.position(endPos);
  187. }
  188. PropertyMap map = new PropertyMap(mapName, blockType);
  189. // read the values
  190. while(bbBlock.hasRemaining()) {
  191. int valLen = bbBlock.getShort();
  192. int endPos = bbBlock.position() + valLen - 2;
  193. byte flag = bbBlock.get();
  194. DataType dataType = DataType.fromByte(bbBlock.get());
  195. int nameIdx = bbBlock.getShort();
  196. int dataSize = bbBlock.getShort();
  197. String propName = propNames.get(nameIdx);
  198. PropColumn col = getColumn(dataType, propName, dataSize);
  199. byte[] data = ByteUtil.getBytes(bbBlock, dataSize);
  200. Object value = col.read(data);
  201. map.put(propName, dataType, flag, value);
  202. bbBlock.position(endPos);
  203. }
  204. return map;
  205. }
  206. /**
  207. * Reads a property name from the given data block
  208. */
  209. private String readPropName(ByteBuffer buffer) {
  210. int nameLength = buffer.getShort();
  211. byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength);
  212. return Column.decodeUncompressedText(nameBytes, _database.getCharset());
  213. }
  214. /**
  215. * Gets a PropColumn capable of reading/writing a property of the given
  216. * DataType
  217. */
  218. private PropColumn getColumn(DataType dataType, String propName,
  219. int dataSize) {
  220. if(isPseudoGuidColumn(dataType, propName, dataSize)) {
  221. dataType = DataType.GUID;
  222. }
  223. PropColumn col = _columns.get(dataType);
  224. if(col == null) {
  225. // translate long value types into simple types
  226. DataType colType = dataType;
  227. if(dataType == DataType.MEMO) {
  228. colType = DataType.TEXT;
  229. } else if(dataType == DataType.OLE) {
  230. colType = DataType.BINARY;
  231. }
  232. // create column with ability to read/write the given data type
  233. col = ((colType == DataType.BOOLEAN) ?
  234. new BooleanPropColumn() : new PropColumn());
  235. col.setType(colType);
  236. if(col.isVariableLength()) {
  237. col.setLength((short)colType.getMaxSize());
  238. }
  239. }
  240. return col;
  241. }
  242. private boolean isPseudoGuidColumn(DataType dataType, String propName,
  243. int dataSize) {
  244. // guids seem to be marked as "binary" fields
  245. return((dataType == DataType.BINARY) &&
  246. (dataSize == DataType.GUID.getFixedSize()) &&
  247. PropertyMap.GUID_PROP.equalsIgnoreCase(propName));
  248. }
  249. /**
  250. * Column adapted to work w/out a Table.
  251. */
  252. private class PropColumn extends Column
  253. {
  254. @Override
  255. public Database getDatabase() {
  256. return _database;
  257. }
  258. }
  259. /**
  260. * Normal boolean columns do not write into the actual row data, so we
  261. * need to do a little extra work.
  262. */
  263. private final class BooleanPropColumn extends PropColumn
  264. {
  265. @Override
  266. public Object read(byte[] data) throws IOException {
  267. return ((data[0] != 0) ? Boolean.TRUE : Boolean.FALSE);
  268. }
  269. @Override
  270. public ByteBuffer write(Object obj, int remainingRowLength)
  271. throws IOException
  272. {
  273. ByteBuffer buffer = getPageChannel().createBuffer(1);
  274. buffer.put(((Number)booleanToInteger(obj)).byteValue());
  275. buffer.flip();
  276. return buffer;
  277. }
  278. }
  279. }
  280. }