Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

PropertyMaps.java 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  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. String mapName = Database.toLookupName(map.getName());
  86. _maps.put(mapName, map.merge(_maps.get(mapName)));
  87. }
  88. public Iterator<PropertyMap> iterator() {
  89. return _maps.values().iterator();
  90. }
  91. @Override
  92. public String toString() {
  93. StringBuilder sb = new StringBuilder();
  94. for(Iterator<PropertyMap> iter = iterator(); iter.hasNext(); ) {
  95. sb.append(iter.next());
  96. if(iter.hasNext()) {
  97. sb.append("\n");
  98. }
  99. }
  100. return sb.toString();
  101. }
  102. /**
  103. * Utility class for reading/writing property blocks.
  104. */
  105. static final class Handler
  106. {
  107. /** the current database */
  108. private final Database _database;
  109. /** cache of PropColumns used to read/write property values */
  110. private final Map<DataType,PropColumn> _columns =
  111. new HashMap<DataType,PropColumn>();
  112. Handler(Database database) {
  113. _database = database;
  114. }
  115. /**
  116. * @return a PropertyMaps instance decoded from the given bytes (always
  117. * returns non-{@code null} result).
  118. */
  119. public PropertyMaps read(byte[] propBytes, int objectId)
  120. throws IOException
  121. {
  122. PropertyMaps maps = new PropertyMaps(objectId);
  123. if((propBytes == null) || (propBytes.length == 0)) {
  124. return maps;
  125. }
  126. ByteBuffer bb = ByteBuffer.wrap(propBytes)
  127. .order(PageChannel.DEFAULT_BYTE_ORDER);
  128. // check for known header
  129. boolean knownType = false;
  130. for(byte[] tmpType : JetFormat.PROPERTY_MAP_TYPES) {
  131. if(ByteUtil.matchesRange(bb, bb.position(), tmpType)) {
  132. ByteUtil.forward(bb, tmpType.length);
  133. knownType = true;
  134. break;
  135. }
  136. }
  137. if(!knownType) {
  138. throw new IOException("Unknown property map type " +
  139. ByteUtil.toHexString(bb, 4));
  140. }
  141. // parse each data "chunk"
  142. List<String> propNames = null;
  143. while(bb.hasRemaining()) {
  144. int len = bb.getInt();
  145. short type = bb.getShort();
  146. int endPos = bb.position() + len - 6;
  147. ByteBuffer bbBlock = PageChannel.narrowBuffer(bb, bb.position(),
  148. endPos);
  149. if(type == PROPERTY_NAME_LIST) {
  150. propNames = readPropertyNames(bbBlock);
  151. } else {
  152. maps.put(readPropertyValues(bbBlock, propNames, type));
  153. }
  154. bb.position(endPos);
  155. }
  156. return maps;
  157. }
  158. /**
  159. * @return the property names parsed from the given data chunk
  160. */
  161. private List<String> readPropertyNames(ByteBuffer bbBlock) {
  162. List<String> names = new ArrayList<String>();
  163. while(bbBlock.hasRemaining()) {
  164. names.add(readPropName(bbBlock));
  165. }
  166. return names;
  167. }
  168. /**
  169. * @return the PropertyMap created from the values parsed from the given
  170. * data chunk combined with the given property names
  171. */
  172. private PropertyMap readPropertyValues(
  173. ByteBuffer bbBlock, List<String> propNames, short blockType)
  174. throws IOException
  175. {
  176. String mapName = DEFAULT_NAME;
  177. if(bbBlock.hasRemaining()) {
  178. // read the map name, if any
  179. int nameBlockLen = bbBlock.getInt();
  180. int endPos = bbBlock.position() + nameBlockLen - 4;
  181. if(nameBlockLen > 6) {
  182. mapName = readPropName(bbBlock);
  183. }
  184. bbBlock.position(endPos);
  185. }
  186. PropertyMap map = new PropertyMap(mapName, blockType);
  187. // read the values
  188. while(bbBlock.hasRemaining()) {
  189. int valLen = bbBlock.getShort();
  190. int endPos = bbBlock.position() + valLen - 2;
  191. byte flag = bbBlock.get();
  192. DataType dataType = DataType.fromByte(bbBlock.get());
  193. int nameIdx = bbBlock.getShort();
  194. int dataSize = bbBlock.getShort();
  195. String propName = propNames.get(nameIdx);
  196. PropColumn col = getColumn(dataType, propName, dataSize);
  197. byte[] data = ByteUtil.getBytes(bbBlock, dataSize);
  198. Object value = col.read(data);
  199. map.put(propName, dataType, flag, value);
  200. bbBlock.position(endPos);
  201. }
  202. return map;
  203. }
  204. /**
  205. * Reads a property name from the given data block
  206. */
  207. private String readPropName(ByteBuffer buffer) {
  208. int nameLength = buffer.getShort();
  209. byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength);
  210. return Column.decodeUncompressedText(nameBytes, _database.getCharset());
  211. }
  212. /**
  213. * Gets a PropColumn capable of reading/writing a property of the given
  214. * DataType
  215. */
  216. private PropColumn getColumn(DataType dataType, String propName,
  217. int dataSize) {
  218. if(isPseudoGuidColumn(dataType, propName, dataSize)) {
  219. dataType = DataType.GUID;
  220. }
  221. PropColumn col = _columns.get(dataType);
  222. if(col == null) {
  223. // translate long value types into simple types
  224. DataType colType = dataType;
  225. if(dataType == DataType.MEMO) {
  226. colType = DataType.TEXT;
  227. } else if(dataType == DataType.OLE) {
  228. colType = DataType.BINARY;
  229. }
  230. // create column with ability to read/write the given data type
  231. col = ((colType == DataType.BOOLEAN) ?
  232. new BooleanPropColumn() : new PropColumn());
  233. col.setType(colType);
  234. if(col.isVariableLength()) {
  235. col.setLength((short)colType.getMaxSize());
  236. }
  237. }
  238. return col;
  239. }
  240. private boolean isPseudoGuidColumn(DataType dataType, String propName,
  241. int dataSize) {
  242. // guids seem to be marked as "binary" fields
  243. return((dataType == DataType.BINARY) &&
  244. (dataSize == DataType.GUID.getFixedSize()) &&
  245. PropertyMap.GUID_PROP.equalsIgnoreCase(propName));
  246. }
  247. /**
  248. * Column adapted to work w/out a Table.
  249. */
  250. private class PropColumn extends Column
  251. {
  252. @Override
  253. public Database getDatabase() {
  254. return _database;
  255. }
  256. }
  257. /**
  258. * Normal boolean columns do not write into the actual row data, so we
  259. * need to do a little extra work.
  260. */
  261. private final class BooleanPropColumn extends PropColumn
  262. {
  263. @Override
  264. public Object read(byte[] data) throws IOException {
  265. return ((data[0] != 0) ? Boolean.TRUE : Boolean.FALSE);
  266. }
  267. @Override
  268. public ByteBuffer write(Object obj, int remainingRowLength)
  269. throws IOException
  270. {
  271. ByteBuffer buffer = getPageChannel().createBuffer(1);
  272. buffer.put(((Number)booleanToInteger(obj)).byteValue());
  273. buffer.flip();
  274. return buffer;
  275. }
  276. }
  277. }
  278. }