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.

Column.java 51KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722
  1. /*
  2. Copyright (c) 2005 Health Market Science, Inc.
  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. You can contact Health Market Science at info@healthmarketscience.com
  16. or at the following address:
  17. Health Market Science
  18. 2700 Horizon Drive
  19. Suite 200
  20. King of Prussia, PA 19406
  21. */
  22. package com.healthmarketscience.jackcess;
  23. import java.io.ByteArrayOutputStream;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.ObjectOutputStream;
  27. import java.io.ObjectStreamException;
  28. import java.io.Reader;
  29. import java.io.Serializable;
  30. import java.math.BigDecimal;
  31. import java.math.BigInteger;
  32. import java.nio.ByteBuffer;
  33. import java.nio.ByteOrder;
  34. import java.nio.CharBuffer;
  35. import java.sql.Blob;
  36. import java.sql.Clob;
  37. import java.sql.SQLException;
  38. import java.util.Calendar;
  39. import java.util.Date;
  40. import java.util.List;
  41. import java.util.UUID;
  42. import java.util.regex.Matcher;
  43. import java.util.regex.Pattern;
  44. import com.healthmarketscience.jackcess.scsu.Compress;
  45. import com.healthmarketscience.jackcess.scsu.EndOfInputException;
  46. import com.healthmarketscience.jackcess.scsu.Expand;
  47. import com.healthmarketscience.jackcess.scsu.IllegalInputException;
  48. import org.apache.commons.logging.Log;
  49. import org.apache.commons.logging.LogFactory;
  50. /**
  51. * Access database column definition
  52. * @author Tim McCune
  53. */
  54. public class Column implements Comparable<Column> {
  55. private static final Log LOG = LogFactory.getLog(Column.class);
  56. /**
  57. * Meaningless placeholder object for inserting values in an autonumber
  58. * column. it is not required that this value be used (any passed in value
  59. * is ignored), but using this placeholder may make code more obvious.
  60. */
  61. public static final Object AUTO_NUMBER = "<AUTO_NUMBER>";
  62. /**
  63. * Meaningless placeholder object for updating rows which indicates that a
  64. * given column should keep its existing value.
  65. */
  66. public static final Object KEEP_VALUE = "<KEEP_VALUE>";
  67. /**
  68. * Access stores numeric dates in days. Java stores them in milliseconds.
  69. */
  70. private static final double MILLISECONDS_PER_DAY =
  71. (24L * 60L * 60L * 1000L);
  72. /**
  73. * Access starts counting dates at Jan 1, 1900. Java starts counting
  74. * at Jan 1, 1970. This is the # of millis between them for conversion.
  75. */
  76. private static final long MILLIS_BETWEEN_EPOCH_AND_1900 =
  77. 25569L * (long)MILLISECONDS_PER_DAY;
  78. /**
  79. * Long value (LVAL) type that indicates that the value is stored on the same page
  80. */
  81. private static final byte LONG_VALUE_TYPE_THIS_PAGE = (byte) 0x80;
  82. /**
  83. * Long value (LVAL) type that indicates that the value is stored on another page
  84. */
  85. private static final byte LONG_VALUE_TYPE_OTHER_PAGE = (byte) 0x40;
  86. /**
  87. * Long value (LVAL) type that indicates that the value is stored on multiple other pages
  88. */
  89. private static final byte LONG_VALUE_TYPE_OTHER_PAGES = (byte) 0x00;
  90. /** mask for the fixed len bit */
  91. public static final byte FIXED_LEN_FLAG_MASK = (byte)0x01;
  92. /** mask for the auto number bit */
  93. public static final byte AUTO_NUMBER_FLAG_MASK = (byte)0x04;
  94. /** mask for the auto number guid bit */
  95. public static final byte AUTO_NUMBER_GUID_FLAG_MASK = (byte)0x40;
  96. /** mask for the unknown bit */
  97. public static final byte UNKNOWN_FLAG_MASK = (byte)0x02;
  98. /** pattern matching textual guid strings (allows for optional surrounding
  99. '{' and '}') */
  100. private static final Pattern GUID_PATTERN = Pattern.compile("\\s*[{]?([\\p{XDigit}]{8})-([\\p{XDigit}]{4})-([\\p{XDigit}]{4})-([\\p{XDigit}]{4})-([\\p{XDigit}]{12})[}]?\\s*");
  101. /** header used to indicate unicode text compression */
  102. private static final byte[] TEXT_COMPRESSION_HEADER =
  103. { (byte)0xFF, (byte)0XFE };
  104. /** owning table */
  105. private final Table _table;
  106. /** For text columns, whether or not they are compressed */
  107. private boolean _compressedUnicode = false;
  108. /** Whether or not the column is of variable length */
  109. private boolean _variableLength;
  110. /** Whether or not the column is an autonumber column */
  111. private boolean _autoNumber;
  112. /** Numeric precision */
  113. private byte _precision;
  114. /** Numeric scale */
  115. private byte _scale;
  116. /** Data type */
  117. private DataType _type;
  118. /** Maximum column length */
  119. private short _columnLength;
  120. /** 0-based column number */
  121. private short _columnNumber;
  122. /** index of the data for this column within a list of row data */
  123. private int _columnIndex;
  124. /** Column name */
  125. private String _name;
  126. /** the offset of the fixed data in the row */
  127. private int _fixedDataOffset;
  128. /** the index of the variable length data in the var len offset table */
  129. private int _varLenTableIndex;
  130. /** the auto number generator for this column (if autonumber column) */
  131. private AutoNumberGenerator _autoNumberGenerator;
  132. public Column() {
  133. this(JetFormat.VERSION_4);
  134. }
  135. public Column(JetFormat format) {
  136. _table = null;
  137. }
  138. /**
  139. * Only used by unit tests
  140. */
  141. Column(boolean testing, Table table) {
  142. if(!testing) {
  143. throw new IllegalArgumentException();
  144. }
  145. _table = table;
  146. }
  147. /**
  148. * Read a column definition in from a buffer
  149. * @param table owning table
  150. * @param buffer Buffer containing column definition
  151. * @param offset Offset in the buffer at which the column definition starts
  152. */
  153. public Column(Table table, ByteBuffer buffer, int offset)
  154. throws IOException
  155. {
  156. _table = table;
  157. if (LOG.isDebugEnabled()) {
  158. LOG.debug("Column def block:\n" + ByteUtil.toHexString(buffer, offset, 25));
  159. }
  160. setType(DataType.fromByte(buffer.get(offset + getFormat().OFFSET_COLUMN_TYPE)));
  161. _columnNumber = buffer.getShort(offset + getFormat().OFFSET_COLUMN_NUMBER);
  162. _columnLength = buffer.getShort(offset + getFormat().OFFSET_COLUMN_LENGTH);
  163. if (_type.getHasScalePrecision()) {
  164. _precision = buffer.get(offset + getFormat().OFFSET_COLUMN_PRECISION);
  165. _scale = buffer.get(offset + getFormat().OFFSET_COLUMN_SCALE);
  166. }
  167. byte flags = buffer.get(offset + getFormat().OFFSET_COLUMN_FLAGS);
  168. _variableLength = ((flags & FIXED_LEN_FLAG_MASK) == 0);
  169. _autoNumber = ((flags & (AUTO_NUMBER_FLAG_MASK | AUTO_NUMBER_GUID_FLAG_MASK)) != 0);
  170. setAutoNumberGenerator();
  171. _compressedUnicode = ((buffer.get(offset +
  172. getFormat().OFFSET_COLUMN_COMPRESSED_UNICODE) & 1) == 1);
  173. if(_variableLength) {
  174. _varLenTableIndex = buffer.getShort(offset + getFormat().OFFSET_COLUMN_VARIABLE_TABLE_INDEX);
  175. } else {
  176. _fixedDataOffset = buffer.getShort(offset + getFormat().OFFSET_COLUMN_FIXED_DATA_OFFSET);
  177. }
  178. }
  179. public Table getTable() {
  180. return _table;
  181. }
  182. public JetFormat getFormat() {
  183. return getTable().getFormat();
  184. }
  185. public PageChannel getPageChannel() {
  186. return getTable().getPageChannel();
  187. }
  188. public String getName() {
  189. return _name;
  190. }
  191. public void setName(String name) {
  192. _name = name;
  193. }
  194. public boolean isVariableLength() {
  195. return _variableLength;
  196. }
  197. public void setVariableLength(boolean variableLength) {
  198. _variableLength = variableLength;
  199. }
  200. public boolean isAutoNumber() {
  201. return _autoNumber;
  202. }
  203. public void setAutoNumber(boolean autoNumber) {
  204. _autoNumber = autoNumber;
  205. setAutoNumberGenerator();
  206. }
  207. public short getColumnNumber() {
  208. return _columnNumber;
  209. }
  210. public void setColumnNumber(short newColumnNumber) {
  211. _columnNumber = newColumnNumber;
  212. }
  213. public int getColumnIndex() {
  214. return _columnIndex;
  215. }
  216. public void setColumnIndex(int newColumnIndex) {
  217. _columnIndex = newColumnIndex;
  218. }
  219. /**
  220. * Also sets the length and the variable length flag, inferred from the
  221. * type. For types with scale/precision, sets the scale and precision to
  222. * default values.
  223. */
  224. public void setType(DataType type) {
  225. _type = type;
  226. if(!type.isVariableLength()) {
  227. setLength((short)type.getFixedSize());
  228. } else if(!type.isLongValue()) {
  229. setLength((short)type.getDefaultSize());
  230. }
  231. setVariableLength(type.isVariableLength());
  232. if(type.getHasScalePrecision()) {
  233. setScale((byte)type.getDefaultScale());
  234. setPrecision((byte)type.getDefaultPrecision());
  235. }
  236. }
  237. public DataType getType() {
  238. return _type;
  239. }
  240. public int getSQLType() throws SQLException {
  241. return _type.getSQLType();
  242. }
  243. public void setSQLType(int type) throws SQLException {
  244. setSQLType(type, 0);
  245. }
  246. public void setSQLType(int type, int lengthInUnits) throws SQLException {
  247. setType(DataType.fromSQLType(type, lengthInUnits));
  248. }
  249. public boolean isCompressedUnicode() {
  250. return _compressedUnicode;
  251. }
  252. public void setCompressedUnicode(boolean newCompessedUnicode) {
  253. _compressedUnicode = newCompessedUnicode;
  254. }
  255. public byte getPrecision() {
  256. return _precision;
  257. }
  258. public void setPrecision(byte newPrecision) {
  259. _precision = newPrecision;
  260. }
  261. public byte getScale() {
  262. return _scale;
  263. }
  264. public void setScale(byte newScale) {
  265. _scale = newScale;
  266. }
  267. public void setLength(short length) {
  268. _columnLength = length;
  269. }
  270. public short getLength() {
  271. return _columnLength;
  272. }
  273. public void setLengthInUnits(short unitLength) {
  274. setLength((short)getType().fromUnitSize(unitLength));
  275. }
  276. public short getLengthInUnits() {
  277. return (short)getType().toUnitSize(getLength());
  278. }
  279. public void setVarLenTableIndex(int idx) {
  280. _varLenTableIndex = idx;
  281. }
  282. public int getVarLenTableIndex() {
  283. return _varLenTableIndex;
  284. }
  285. public void setFixedDataOffset(int newOffset) {
  286. _fixedDataOffset = newOffset;
  287. }
  288. public int getFixedDataOffset() {
  289. return _fixedDataOffset;
  290. }
  291. private void setAutoNumberGenerator()
  292. {
  293. if(!_autoNumber || (_type == null)) {
  294. _autoNumberGenerator = null;
  295. return;
  296. }
  297. if((_autoNumberGenerator != null) &&
  298. (_autoNumberGenerator.getType() == _type)) {
  299. // keep existing
  300. return;
  301. }
  302. switch(_type) {
  303. case LONG:
  304. _autoNumberGenerator = new LongAutoNumberGenerator();
  305. break;
  306. case GUID:
  307. _autoNumberGenerator = new GuidAutoNumberGenerator();
  308. break;
  309. default:
  310. throw new RuntimeException("Unexpected autoNumber column type " + _type);
  311. }
  312. }
  313. /**
  314. * Returns the AutoNumberGenerator for this column if this is an autonumber
  315. * column, {@code null} otherwise.
  316. */
  317. public AutoNumberGenerator getAutoNumberGenerator() {
  318. return _autoNumberGenerator;
  319. }
  320. /**
  321. * Checks that this column definition is valid.
  322. *
  323. * @throws IllegalArgumentException if this column definition is invalid.
  324. */
  325. public void validate(JetFormat format) {
  326. if(getType() == null) {
  327. throw new IllegalArgumentException("must have type");
  328. }
  329. Database.validateIdentifierName(getName(), format.MAX_COLUMN_NAME_LENGTH,
  330. "column");
  331. if(isVariableLength() != getType().isVariableLength()) {
  332. throw new IllegalArgumentException("invalid variable length setting");
  333. }
  334. if(!isVariableLength()) {
  335. if(getLength() != getType().getFixedSize()) {
  336. if(getLength() < getType().getFixedSize()) {
  337. throw new IllegalArgumentException("invalid fixed length size");
  338. }
  339. LOG.warn("Column length " + getLength() +
  340. " longer than expected fixed size " +
  341. getType().getFixedSize());
  342. }
  343. } else if(!getType().isLongValue()) {
  344. if(!getType().isValidSize(getLength())) {
  345. throw new IllegalArgumentException("var length out of range");
  346. }
  347. }
  348. if(getType().getHasScalePrecision()) {
  349. if(!getType().isValidScale(getScale())) {
  350. throw new IllegalArgumentException(
  351. "Scale must be from " + getType().getMinScale() + " to " +
  352. getType().getMaxScale() + " inclusive");
  353. }
  354. if(!getType().isValidPrecision(getPrecision())) {
  355. throw new IllegalArgumentException(
  356. "Precision must be from " + getType().getMinPrecision() + " to " +
  357. getType().getMaxPrecision() + " inclusive");
  358. }
  359. }
  360. if(isAutoNumber()) {
  361. if((getType() != DataType.LONG) && (getType() != DataType.GUID)) {
  362. throw new IllegalArgumentException(
  363. "Auto number column must be long integer or guid");
  364. }
  365. }
  366. if(isCompressedUnicode()) {
  367. if((getType() != DataType.TEXT) &&
  368. (getType() != DataType.MEMO)) {
  369. throw new IllegalArgumentException(
  370. "Only textual columns allow unicode compression (text/memo)");
  371. }
  372. }
  373. }
  374. /**
  375. * Deserialize a raw byte value for this column into an Object
  376. * @param data The raw byte value
  377. * @return The deserialized Object
  378. */
  379. public Object read(byte[] data) throws IOException {
  380. return read(data, ByteOrder.LITTLE_ENDIAN);
  381. }
  382. /**
  383. * Deserialize a raw byte value for this column into an Object
  384. * @param data The raw byte value
  385. * @param order Byte order in which the raw value is stored
  386. * @return The deserialized Object
  387. */
  388. public Object read(byte[] data, ByteOrder order) throws IOException {
  389. ByteBuffer buffer = ByteBuffer.wrap(data);
  390. buffer.order(order);
  391. if (_type == DataType.BOOLEAN) {
  392. throw new IOException("Tried to read a boolean from data instead of null mask.");
  393. } else if (_type == DataType.BYTE) {
  394. return Byte.valueOf(buffer.get());
  395. } else if (_type == DataType.INT) {
  396. return Short.valueOf(buffer.getShort());
  397. } else if (_type == DataType.LONG) {
  398. return Integer.valueOf(buffer.getInt());
  399. } else if (_type == DataType.DOUBLE) {
  400. return Double.valueOf(buffer.getDouble());
  401. } else if (_type == DataType.FLOAT) {
  402. return Float.valueOf(buffer.getFloat());
  403. } else if (_type == DataType.SHORT_DATE_TIME) {
  404. return readDateValue(buffer);
  405. } else if (_type == DataType.BINARY) {
  406. return data;
  407. } else if (_type == DataType.TEXT) {
  408. return decodeTextValue(data);
  409. } else if (_type == DataType.MONEY) {
  410. return readCurrencyValue(buffer);
  411. } else if (_type == DataType.OLE) {
  412. if (data.length > 0) {
  413. return readLongValue(data);
  414. }
  415. return null;
  416. } else if (_type == DataType.MEMO) {
  417. if (data.length > 0) {
  418. return readLongStringValue(data);
  419. }
  420. return null;
  421. } else if (_type == DataType.NUMERIC) {
  422. return readNumericValue(buffer);
  423. } else if (_type == DataType.GUID) {
  424. return readGUIDValue(buffer, order);
  425. } else if ((_type == DataType.UNKNOWN_0D) ||
  426. (_type == DataType.UNKNOWN_11)) {
  427. // treat like "binary" data
  428. return data;
  429. } else {
  430. throw new IOException("Unrecognized data type: " + _type);
  431. }
  432. }
  433. /**
  434. * @param lvalDefinition Column value that points to an LVAL record
  435. * @return The LVAL data
  436. */
  437. private byte[] readLongValue(byte[] lvalDefinition)
  438. throws IOException
  439. {
  440. ByteBuffer def = ByteBuffer.wrap(lvalDefinition);
  441. def.order(ByteOrder.LITTLE_ENDIAN);
  442. int length = ByteUtil.get3ByteInt(def);
  443. // bail out gracefully here as we don't understand the format
  444. if (length < 0)
  445. {
  446. return null;
  447. }
  448. byte[] rtn = new byte[length];
  449. byte type = def.get();
  450. if(type == LONG_VALUE_TYPE_THIS_PAGE) {
  451. // inline long value
  452. def.getInt(); //Skip over lval_dp
  453. def.getInt(); //Skip over unknown
  454. def.get(rtn);
  455. } else {
  456. // long value on other page(s)
  457. if (lvalDefinition.length != getFormat().SIZE_LONG_VALUE_DEF) {
  458. throw new IOException("Expected " + getFormat().SIZE_LONG_VALUE_DEF +
  459. " bytes in long value definition, but found " +
  460. lvalDefinition.length);
  461. }
  462. int rowNum = ByteUtil.getUnsignedByte(def);
  463. int pageNum = ByteUtil.get3ByteInt(def, def.position());
  464. ByteBuffer lvalPage = getPageChannel().createPageBuffer();
  465. switch (type) {
  466. case LONG_VALUE_TYPE_OTHER_PAGE:
  467. {
  468. getPageChannel().readPage(lvalPage, pageNum);
  469. short rowStart = Table.findRowStart(lvalPage, rowNum, getFormat());
  470. short rowEnd = Table.findRowEnd(lvalPage, rowNum, getFormat());
  471. if((rowEnd - rowStart) != length) {
  472. throw new IOException("Unexpected lval row length");
  473. }
  474. lvalPage.position(rowStart);
  475. lvalPage.get(rtn);
  476. }
  477. break;
  478. case LONG_VALUE_TYPE_OTHER_PAGES:
  479. ByteBuffer rtnBuf = ByteBuffer.wrap(rtn);
  480. int remainingLen = length;
  481. while(remainingLen > 0) {
  482. lvalPage.clear();
  483. getPageChannel().readPage(lvalPage, pageNum);
  484. short rowStart = Table.findRowStart(lvalPage, rowNum, getFormat());
  485. short rowEnd = Table.findRowEnd(lvalPage, rowNum, getFormat());
  486. // read next page information
  487. lvalPage.position(rowStart);
  488. rowNum = ByteUtil.getUnsignedByte(lvalPage);
  489. pageNum = ByteUtil.get3ByteInt(lvalPage);
  490. // update rowEnd and remainingLen based on chunkLength
  491. int chunkLength = (rowEnd - rowStart) - 4;
  492. if(chunkLength > remainingLen) {
  493. rowEnd = (short)(rowEnd - (chunkLength - remainingLen));
  494. chunkLength = remainingLen;
  495. }
  496. remainingLen -= chunkLength;
  497. lvalPage.limit(rowEnd);
  498. rtnBuf.put(lvalPage);
  499. }
  500. break;
  501. default:
  502. throw new IOException("Unrecognized long value type: " + type);
  503. }
  504. }
  505. return rtn;
  506. }
  507. /**
  508. * @param lvalDefinition Column value that points to an LVAL record
  509. * @return The LVAL data
  510. */
  511. private String readLongStringValue(byte[] lvalDefinition)
  512. throws IOException
  513. {
  514. byte[] binData = readLongValue(lvalDefinition);
  515. if(binData == null) {
  516. return null;
  517. }
  518. return decodeTextValue(binData);
  519. }
  520. /**
  521. * Decodes "Currency" values.
  522. *
  523. * @param buffer Column value that points to currency data
  524. * @return BigDecimal representing the monetary value
  525. * @throws IOException if the value cannot be parsed
  526. */
  527. private BigDecimal readCurrencyValue(ByteBuffer buffer)
  528. throws IOException
  529. {
  530. if(buffer.remaining() != 8) {
  531. throw new IOException("Invalid money value.");
  532. }
  533. return new BigDecimal(BigInteger.valueOf(buffer.getLong(0)), 4);
  534. }
  535. /**
  536. * Writes "Currency" values.
  537. */
  538. private void writeCurrencyValue(ByteBuffer buffer, Object value)
  539. throws IOException
  540. {
  541. try {
  542. BigDecimal decVal = toBigDecimal(value);
  543. // adjust scale (will cause the an ArithmeticException if number has too
  544. // many decimal places)
  545. decVal = decVal.setScale(4);
  546. // now, remove scale and convert to long (this will throw if the value is
  547. // too big)
  548. buffer.putLong(decVal.movePointRight(4).longValueExact());
  549. } catch(ArithmeticException e) {
  550. throw (IOException)
  551. new IOException("Currency value out of range").initCause(e);
  552. }
  553. }
  554. /**
  555. * Decodes a NUMERIC field.
  556. */
  557. private BigDecimal readNumericValue(ByteBuffer buffer)
  558. {
  559. boolean negate = (buffer.get() != 0);
  560. byte[] tmpArr = new byte[16];
  561. buffer.get(tmpArr);
  562. if(buffer.order() != ByteOrder.BIG_ENDIAN) {
  563. fixNumericByteOrder(tmpArr);
  564. }
  565. BigInteger intVal = new BigInteger(tmpArr);
  566. if(negate) {
  567. intVal = intVal.negate();
  568. }
  569. return new BigDecimal(intVal, getScale());
  570. }
  571. /**
  572. * Writes a numeric value.
  573. */
  574. private void writeNumericValue(ByteBuffer buffer, Object value)
  575. throws IOException
  576. {
  577. try {
  578. BigDecimal decVal = toBigDecimal(value);
  579. boolean negative = (decVal.compareTo(BigDecimal.ZERO) < 0);
  580. if(negative) {
  581. decVal = decVal.negate();
  582. }
  583. // write sign byte
  584. buffer.put(negative ? (byte)0x80 : (byte)0);
  585. // adjust scale according to this column type (will cause the an
  586. // ArithmeticException if number has too many decimal places)
  587. decVal = decVal.setScale(getScale());
  588. // check precision
  589. if(decVal.precision() > getPrecision()) {
  590. throw new IOException(
  591. "Numeric value is too big for specified precision "
  592. + getPrecision() + ": " + decVal);
  593. }
  594. // convert to unscaled BigInteger, big-endian bytes
  595. byte[] intValBytes = decVal.unscaledValue().toByteArray();
  596. int maxByteLen = getType().getFixedSize() - 1;
  597. if(intValBytes.length > maxByteLen) {
  598. throw new IOException("Too many bytes for valid BigInteger?");
  599. }
  600. if(intValBytes.length < maxByteLen) {
  601. byte[] tmpBytes = new byte[maxByteLen];
  602. System.arraycopy(intValBytes, 0, tmpBytes,
  603. (maxByteLen - intValBytes.length),
  604. intValBytes.length);
  605. intValBytes = tmpBytes;
  606. }
  607. if(buffer.order() != ByteOrder.BIG_ENDIAN) {
  608. fixNumericByteOrder(intValBytes);
  609. }
  610. buffer.put(intValBytes);
  611. } catch(ArithmeticException e) {
  612. throw (IOException)
  613. new IOException("Numeric value out of range").initCause(e);
  614. }
  615. }
  616. /**
  617. * Decodes a date value.
  618. */
  619. private Date readDateValue(ByteBuffer buffer)
  620. {
  621. // seems access stores dates in the local timezone. guess you just hope
  622. // you read it in the same timezone in which it was written!
  623. long dateBits = buffer.getLong();
  624. long time = fromDateDouble(Double.longBitsToDouble(dateBits));
  625. return new DateExt(time, dateBits);
  626. }
  627. /**
  628. * Returns a java long time value converted from an access date double.
  629. */
  630. private static long fromDateDouble(double value)
  631. {
  632. long time = Math.round(value * MILLISECONDS_PER_DAY);
  633. time -= MILLIS_BETWEEN_EPOCH_AND_1900;
  634. time -= getTimeZoneOffset(time);
  635. return time;
  636. }
  637. /**
  638. * Writes a date value.
  639. */
  640. private void writeDateValue(ByteBuffer buffer, Object value)
  641. {
  642. if(value == null) {
  643. buffer.putDouble(0d);
  644. } else if(value instanceof DateExt) {
  645. // this is a Date value previously read from readDateValue(). use the
  646. // original bits to store the value so we don't lose any precision
  647. buffer.putLong(((DateExt)value).getDateBits());
  648. } else {
  649. buffer.putDouble(toDateDouble(value));
  650. }
  651. }
  652. /**
  653. * Returns an access date double converted from a java Date/Calendar/Number
  654. * time value.
  655. */
  656. private static double toDateDouble(Object value)
  657. {
  658. // seems access stores dates in the local timezone. guess you just
  659. // hope you read it in the same timezone in which it was written!
  660. long time = ((value instanceof Date) ?
  661. ((Date)value).getTime() :
  662. ((value instanceof Calendar) ?
  663. ((Calendar)value).getTimeInMillis() :
  664. ((Number)value).longValue()));
  665. time += getTimeZoneOffset(time);
  666. time += MILLIS_BETWEEN_EPOCH_AND_1900;
  667. return time / MILLISECONDS_PER_DAY;
  668. }
  669. /**
  670. * Gets the timezone offset from UTC for the given time (including DST).
  671. */
  672. private static long getTimeZoneOffset(long time)
  673. {
  674. Calendar c = Calendar.getInstance();
  675. c.setTimeInMillis(time);
  676. return ((long)c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET));
  677. }
  678. /**
  679. * Decodes a GUID value.
  680. */
  681. private String readGUIDValue(ByteBuffer buffer, ByteOrder order)
  682. {
  683. if(order != ByteOrder.BIG_ENDIAN) {
  684. byte[] tmpArr = new byte[16];
  685. buffer.get(tmpArr);
  686. // the first 3 guid components are integer components which need to
  687. // respect endianness, so swap 4-byte int, 2-byte int, 2-byte int
  688. ByteUtil.swap4Bytes(tmpArr, 0);
  689. ByteUtil.swap2Bytes(tmpArr, 4);
  690. ByteUtil.swap2Bytes(tmpArr, 6);
  691. buffer = ByteBuffer.wrap(tmpArr);
  692. }
  693. StringBuilder sb = new StringBuilder(22);
  694. sb.append("{");
  695. sb.append(ByteUtil.toHexString(buffer, 0, 4,
  696. false));
  697. sb.append("-");
  698. sb.append(ByteUtil.toHexString(buffer, 4, 2,
  699. false));
  700. sb.append("-");
  701. sb.append(ByteUtil.toHexString(buffer, 6, 2,
  702. false));
  703. sb.append("-");
  704. sb.append(ByteUtil.toHexString(buffer, 8, 2,
  705. false));
  706. sb.append("-");
  707. sb.append(ByteUtil.toHexString(buffer, 10, 6,
  708. false));
  709. sb.append("}");
  710. return (sb.toString());
  711. }
  712. /**
  713. * Writes a GUID value.
  714. */
  715. private void writeGUIDValue(ByteBuffer buffer, Object value,
  716. ByteOrder order)
  717. throws IOException
  718. {
  719. Matcher m = GUID_PATTERN.matcher(toCharSequence(value));
  720. if(m.matches()) {
  721. ByteBuffer origBuffer = null;
  722. byte[] tmpBuf = null;
  723. if(order != ByteOrder.BIG_ENDIAN) {
  724. // write to a temp buf so we can do some swapping below
  725. origBuffer = buffer;
  726. tmpBuf = new byte[16];
  727. buffer = ByteBuffer.wrap(tmpBuf);
  728. }
  729. ByteUtil.writeHexString(buffer, m.group(1));
  730. ByteUtil.writeHexString(buffer, m.group(2));
  731. ByteUtil.writeHexString(buffer, m.group(3));
  732. ByteUtil.writeHexString(buffer, m.group(4));
  733. ByteUtil.writeHexString(buffer, m.group(5));
  734. if(tmpBuf != null) {
  735. // the first 3 guid components are integer components which need to
  736. // respect endianness, so swap 4-byte int, 2-byte int, 2-byte int
  737. ByteUtil.swap4Bytes(tmpBuf, 0);
  738. ByteUtil.swap2Bytes(tmpBuf, 4);
  739. ByteUtil.swap2Bytes(tmpBuf, 6);
  740. origBuffer.put(tmpBuf);
  741. }
  742. } else {
  743. throw new IOException("Invalid GUID: " + value);
  744. }
  745. }
  746. /**
  747. * Write an LVAL column into a ByteBuffer inline if it fits, otherwise in
  748. * other data page(s).
  749. * @param value Value of the LVAL column
  750. * @return A buffer containing the LVAL definition and (possibly) the column
  751. * value (unless written to other pages)
  752. */
  753. public ByteBuffer writeLongValue(byte[] value,
  754. int remainingRowLength) throws IOException
  755. {
  756. if(value.length > getType().getMaxSize()) {
  757. throw new IOException("value too big for column, max " +
  758. getType().getMaxSize() + ", got " +
  759. value.length);
  760. }
  761. // determine which type to write
  762. byte type = 0;
  763. int lvalDefLen = getFormat().SIZE_LONG_VALUE_DEF;
  764. if(((getFormat().SIZE_LONG_VALUE_DEF + value.length) <= remainingRowLength)
  765. && (value.length <= getFormat().MAX_INLINE_LONG_VALUE_SIZE)) {
  766. type = LONG_VALUE_TYPE_THIS_PAGE;
  767. lvalDefLen += value.length;
  768. } else if(value.length <= getFormat().MAX_LONG_VALUE_ROW_SIZE) {
  769. type = LONG_VALUE_TYPE_OTHER_PAGE;
  770. } else {
  771. type = LONG_VALUE_TYPE_OTHER_PAGES;
  772. }
  773. ByteBuffer def = getPageChannel().createBuffer(lvalDefLen);
  774. ByteUtil.put3ByteInt(def, value.length);
  775. def.put(type);
  776. if(type == LONG_VALUE_TYPE_THIS_PAGE) {
  777. // write long value inline
  778. def.putInt(0);
  779. def.putInt(0); //Unknown
  780. def.put(value);
  781. } else {
  782. TempPageHolder lvalBufferH = getTable().getLongValueBuffer();
  783. ByteBuffer lvalPage = null;
  784. int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER;
  785. byte firstLvalRow = 0;
  786. // write other page(s)
  787. switch(type) {
  788. case LONG_VALUE_TYPE_OTHER_PAGE:
  789. lvalPage = getLongValuePage(value.length, lvalBufferH);
  790. firstLvalPageNum = lvalBufferH.getPageNumber();
  791. firstLvalRow = (byte)Table.addDataPageRow(lvalPage, value.length,
  792. getFormat(), 0);
  793. lvalPage.put(value);
  794. getPageChannel().writePage(lvalPage, firstLvalPageNum);
  795. break;
  796. case LONG_VALUE_TYPE_OTHER_PAGES:
  797. ByteBuffer buffer = ByteBuffer.wrap(value);
  798. int remainingLen = buffer.remaining();
  799. buffer.limit(0);
  800. lvalPage = getLongValuePage(getFormat().MAX_LONG_VALUE_ROW_SIZE,
  801. lvalBufferH);
  802. firstLvalPageNum = lvalBufferH.getPageNumber();
  803. int lvalPageNum = firstLvalPageNum;
  804. ByteBuffer nextLvalPage = null;
  805. int nextLvalPageNum = 0;
  806. while(remainingLen > 0) {
  807. lvalPage.clear();
  808. // figure out how much we will put in this page (we need 4 bytes for
  809. // the next page pointer)
  810. int chunkLength = Math.min(getFormat().MAX_LONG_VALUE_ROW_SIZE - 4,
  811. remainingLen);
  812. // figure out if we will need another page, and if so, allocate it
  813. if(chunkLength < remainingLen) {
  814. // force a new page to be allocated
  815. lvalBufferH.clear();
  816. nextLvalPage = getLongValuePage(
  817. getFormat().MAX_LONG_VALUE_ROW_SIZE, lvalBufferH);
  818. nextLvalPageNum = lvalBufferH.getPageNumber();
  819. } else {
  820. nextLvalPage = null;
  821. nextLvalPageNum = 0;
  822. }
  823. // add row to this page
  824. byte lvalRow = (byte)Table.addDataPageRow(lvalPage, chunkLength + 4,
  825. getFormat(), 0);
  826. // write next page info (we'll always be writing into row 0 for
  827. // newly created pages)
  828. lvalPage.put((byte)0); // row number
  829. ByteUtil.put3ByteInt(lvalPage, nextLvalPageNum); // page number
  830. // write this page's chunk of data
  831. buffer.limit(buffer.limit() + chunkLength);
  832. lvalPage.put(buffer);
  833. remainingLen -= chunkLength;
  834. // write new page to database
  835. getPageChannel().writePage(lvalPage, lvalPageNum);
  836. if(lvalPageNum == firstLvalPageNum) {
  837. // save initial row info
  838. firstLvalRow = lvalRow;
  839. } else {
  840. // check assertion that we wrote to row 0 for all subsequent pages
  841. if(lvalRow != (byte)0) {
  842. throw new IllegalStateException("Expected row 0, but was " +
  843. lvalRow);
  844. }
  845. }
  846. // move to next page
  847. lvalPage = nextLvalPage;
  848. lvalPageNum = nextLvalPageNum;
  849. }
  850. break;
  851. default:
  852. throw new IOException("Unrecognized long value type: " + type);
  853. }
  854. // update def
  855. def.put(firstLvalRow);
  856. ByteUtil.put3ByteInt(def, firstLvalPageNum);
  857. def.putInt(0); //Unknown
  858. }
  859. def.flip();
  860. return def;
  861. }
  862. /**
  863. * Writes the header info for a long value page.
  864. */
  865. private void writeLongValueHeader(ByteBuffer lvalPage)
  866. {
  867. lvalPage.put(PageTypes.DATA); //Page type
  868. lvalPage.put((byte) 1); //Unknown
  869. lvalPage.putShort((short)getFormat().PAGE_INITIAL_FREE_SPACE); //Free space
  870. lvalPage.put((byte) 'L');
  871. lvalPage.put((byte) 'V');
  872. lvalPage.put((byte) 'A');
  873. lvalPage.put((byte) 'L');
  874. lvalPage.putInt(0); //unknown
  875. lvalPage.putShort((short)0); // num rows in page
  876. }
  877. /**
  878. * Returns a long value data page with space for data of the given length.
  879. */
  880. private ByteBuffer getLongValuePage(int dataLength,
  881. TempPageHolder lvalBufferH)
  882. throws IOException
  883. {
  884. ByteBuffer lvalPage = null;
  885. if(lvalBufferH.getPageNumber() != PageChannel.INVALID_PAGE_NUMBER) {
  886. lvalPage = lvalBufferH.getPage(getPageChannel());
  887. if(Table.rowFitsOnDataPage(dataLength, lvalPage, getFormat())) {
  888. // the current page has space
  889. return lvalPage;
  890. }
  891. }
  892. // need new page
  893. lvalPage = lvalBufferH.setNewPage(getPageChannel());
  894. writeLongValueHeader(lvalPage);
  895. return lvalPage;
  896. }
  897. /**
  898. * Serialize an Object into a raw byte value for this column in little endian order
  899. * @param obj Object to serialize
  900. * @return A buffer containing the bytes
  901. */
  902. public ByteBuffer write(Object obj, int remainingRowLength)
  903. throws IOException
  904. {
  905. return write(obj, remainingRowLength, ByteOrder.LITTLE_ENDIAN);
  906. }
  907. /**
  908. * Serialize an Object into a raw byte value for this column
  909. * @param obj Object to serialize
  910. * @param order Order in which to serialize
  911. * @return A buffer containing the bytes
  912. */
  913. public ByteBuffer write(Object obj, int remainingRowLength, ByteOrder order)
  914. throws IOException
  915. {
  916. if(isRawData(obj)) {
  917. // just slap it right in (not for the faint of heart!)
  918. return ByteBuffer.wrap(((RawData)obj).getBytes());
  919. }
  920. if(!isVariableLength()) {
  921. return writeFixedLengthField(obj, order);
  922. }
  923. // var length column
  924. if(!getType().isLongValue()) {
  925. // this is an "inline" var length field
  926. switch(getType()) {
  927. case NUMERIC:
  928. // don't ask me why numerics are "var length" columns...
  929. ByteBuffer buffer = getPageChannel().createBuffer(
  930. getType().getFixedSize(), order);
  931. writeNumericValue(buffer, obj);
  932. buffer.flip();
  933. return buffer;
  934. case TEXT:
  935. byte[] encodedData = encodeTextValue(
  936. obj, 0, getLengthInUnits(), false).array();
  937. obj = encodedData;
  938. break;
  939. case BINARY:
  940. case UNKNOWN_0D:
  941. // should already be "encoded"
  942. break;
  943. default:
  944. throw new RuntimeException("unexpected inline var length type: " +
  945. getType());
  946. }
  947. ByteBuffer buffer = ByteBuffer.wrap(toByteArray(obj));
  948. buffer.order(order);
  949. return buffer;
  950. }
  951. // var length, long value column
  952. switch(getType()) {
  953. case OLE:
  954. // should already be "encoded"
  955. break;
  956. case MEMO:
  957. int maxMemoChars = DataType.MEMO.toUnitSize(DataType.MEMO.getMaxSize());
  958. obj = encodeTextValue(obj, 0, maxMemoChars, false).array();
  959. break;
  960. default:
  961. throw new RuntimeException("unexpected var length, long value type: " +
  962. getType());
  963. }
  964. // create long value buffer
  965. return writeLongValue(toByteArray(obj), remainingRowLength);
  966. }
  967. /**
  968. * Serialize an Object into a raw byte value for this column
  969. * @param obj Object to serialize
  970. * @param order Order in which to serialize
  971. * @return A buffer containing the bytes
  972. */
  973. public ByteBuffer writeFixedLengthField(Object obj, ByteOrder order)
  974. throws IOException
  975. {
  976. int size = getType().getFixedSize(_columnLength);
  977. // create buffer for data
  978. ByteBuffer buffer = getPageChannel().createBuffer(size, order);
  979. // since booleans are not written by this method, it's safe to convert any
  980. // incoming boolean into an integer.
  981. obj = booleanToInteger(obj);
  982. switch(getType()) {
  983. case BOOLEAN:
  984. //Do nothing
  985. break;
  986. case BYTE:
  987. buffer.put(toNumber(obj).byteValue());
  988. break;
  989. case INT:
  990. buffer.putShort(toNumber(obj).shortValue());
  991. break;
  992. case LONG:
  993. buffer.putInt(toNumber(obj).intValue());
  994. break;
  995. case MONEY:
  996. writeCurrencyValue(buffer, obj);
  997. break;
  998. case FLOAT:
  999. buffer.putFloat(toNumber(obj).floatValue());
  1000. break;
  1001. case DOUBLE:
  1002. buffer.putDouble(toNumber(obj).doubleValue());
  1003. break;
  1004. case SHORT_DATE_TIME:
  1005. writeDateValue(buffer, obj);
  1006. break;
  1007. case TEXT:
  1008. // apparently text numeric values are also occasionally written as fixed
  1009. // length...
  1010. int numChars = getLengthInUnits();
  1011. // force uncompressed encoding for fixed length text
  1012. buffer.put(encodeTextValue(obj, numChars, numChars, true));
  1013. break;
  1014. case GUID:
  1015. writeGUIDValue(buffer, obj, order);
  1016. break;
  1017. case NUMERIC:
  1018. // yes, that's right, occasionally numeric values are written as fixed
  1019. // length...
  1020. writeNumericValue(buffer, obj);
  1021. break;
  1022. case BINARY:
  1023. case UNKNOWN_0D:
  1024. case UNKNOWN_11:
  1025. byte[] bytes = toByteArray(obj);
  1026. if(bytes.length != getLength()) {
  1027. throw new IOException("Invalid fixed size binary data, size "
  1028. + getLength() + ", got " + bytes.length);
  1029. }
  1030. buffer.put(bytes);
  1031. break;
  1032. default:
  1033. throw new IOException("Unsupported data type: " + getType());
  1034. }
  1035. buffer.flip();
  1036. return buffer;
  1037. }
  1038. /**
  1039. * Decodes a compressed or uncompressed text value.
  1040. */
  1041. private String decodeTextValue(byte[] data)
  1042. throws IOException
  1043. {
  1044. try {
  1045. // see if data is compressed. the 0xFF, 0xFE sequence indicates that
  1046. // compression is used (sort of, see algorithm below)
  1047. boolean isCompressed = ((data.length > 1) &&
  1048. (data[0] == TEXT_COMPRESSION_HEADER[0]) &&
  1049. (data[1] == TEXT_COMPRESSION_HEADER[1]));
  1050. if(isCompressed) {
  1051. Expand expander = new Expand();
  1052. // this is a whacky compression combo that switches back and forth
  1053. // between compressed/uncompressed using a 0x00 byte (starting in
  1054. // compressed mode)
  1055. StringBuilder textBuf = new StringBuilder(data.length);
  1056. // start after two bytes indicating compression use
  1057. int dataStart = TEXT_COMPRESSION_HEADER.length;
  1058. int dataEnd = dataStart;
  1059. boolean inCompressedMode = true;
  1060. while(dataEnd < data.length) {
  1061. if(data[dataEnd] == (byte)0x00) {
  1062. // handle current segment
  1063. decodeTextSegment(data, dataStart, dataEnd, inCompressedMode,
  1064. expander, textBuf);
  1065. inCompressedMode = !inCompressedMode;
  1066. ++dataEnd;
  1067. dataStart = dataEnd;
  1068. } else {
  1069. ++dataEnd;
  1070. }
  1071. }
  1072. // handle last segment
  1073. decodeTextSegment(data, dataStart, dataEnd, inCompressedMode,
  1074. expander, textBuf);
  1075. return textBuf.toString();
  1076. }
  1077. return decodeUncompressedText(data, getFormat());
  1078. } catch (IllegalInputException e) {
  1079. throw (IOException)
  1080. new IOException("Can't expand text column").initCause(e);
  1081. } catch (EndOfInputException e) {
  1082. throw (IOException)
  1083. new IOException("Can't expand text column").initCause(e);
  1084. }
  1085. }
  1086. /**
  1087. * Decodes a segnment of a text value into the given buffer according to the
  1088. * given status of the segment (compressed/uncompressed).
  1089. */
  1090. private void decodeTextSegment(byte[] data, int dataStart, int dataEnd,
  1091. boolean inCompressedMode, Expand expander,
  1092. StringBuilder textBuf)
  1093. throws IllegalInputException, EndOfInputException
  1094. {
  1095. if(dataEnd <= dataStart) {
  1096. // no data
  1097. return;
  1098. }
  1099. int dataLength = dataEnd - dataStart;
  1100. if(inCompressedMode) {
  1101. // handle compressed data
  1102. byte[] tmpData = new byte[dataLength];
  1103. System.arraycopy(data, dataStart, tmpData, 0, dataLength);
  1104. expander.reset();
  1105. textBuf.append(expander.expand(tmpData));
  1106. } else {
  1107. // handle uncompressed data
  1108. textBuf.append(decodeUncompressedText(data, dataStart, dataLength,
  1109. getFormat()));
  1110. }
  1111. }
  1112. /**
  1113. * @param textBytes bytes of text to decode
  1114. * @return the decoded string
  1115. */
  1116. private static CharBuffer decodeUncompressedText(
  1117. byte[] textBytes, int startPos, int length, JetFormat format)
  1118. {
  1119. return format.CHARSET.decode(ByteBuffer.wrap(textBytes, startPos, length));
  1120. }
  1121. /**
  1122. * Encodes a text value, possibly compressing.
  1123. */
  1124. private ByteBuffer encodeTextValue(Object obj, int minChars, int maxChars,
  1125. boolean forceUncompressed)
  1126. throws IOException
  1127. {
  1128. CharSequence text = toCharSequence(obj);
  1129. if((text.length() > maxChars) || (text.length() < minChars)) {
  1130. throw new IOException("Text is wrong length for " + getType() +
  1131. " column, max " + maxChars
  1132. + ", min " + minChars + ", got " + text.length());
  1133. }
  1134. // may only compress if column type allows it
  1135. if(!forceUncompressed && isCompressedUnicode()) {
  1136. // for now, only do very simple compression (only compress text which is
  1137. // all ascii text)
  1138. if(isAsciiCompressible(text)) {
  1139. byte[] encodedChars = new byte[TEXT_COMPRESSION_HEADER.length +
  1140. text.length()];
  1141. encodedChars[0] = TEXT_COMPRESSION_HEADER[0];
  1142. encodedChars[1] = TEXT_COMPRESSION_HEADER[1];
  1143. for(int i = 0; i < text.length(); ++i) {
  1144. encodedChars[i + TEXT_COMPRESSION_HEADER.length] =
  1145. (byte)text.charAt(i);
  1146. }
  1147. return ByteBuffer.wrap(encodedChars);
  1148. }
  1149. }
  1150. return encodeUncompressedText(text, getFormat());
  1151. }
  1152. /**
  1153. * Returns {@code true} if the given text can be compressed using simple
  1154. * ASCII encoding, {@code false} otherwise.
  1155. */
  1156. private static boolean isAsciiCompressible(CharSequence text) {
  1157. // only attempt to compress > 2 chars (compressing less than 3 chars would
  1158. // not result in a space savings due to the 2 byte compression header)
  1159. if(text.length() <= TEXT_COMPRESSION_HEADER.length) {
  1160. return false;
  1161. }
  1162. // now, see if it is all printable ASCII
  1163. for(int i = 0; i < text.length(); ++i) {
  1164. char c = text.charAt(i);
  1165. if(!Compress.isAsciiCrLfOrTab(c)) {
  1166. return false;
  1167. }
  1168. }
  1169. return true;
  1170. }
  1171. @Override
  1172. public String toString() {
  1173. StringBuilder rtn = new StringBuilder();
  1174. rtn.append("\tName: (" + _table.getName() + ") " + _name);
  1175. rtn.append("\n\tType: 0x" + Integer.toHexString(_type.getValue()) +
  1176. " (" + _type + ")");
  1177. rtn.append("\n\tNumber: " + _columnNumber);
  1178. rtn.append("\n\tLength: " + _columnLength);
  1179. rtn.append("\n\tVariable length: " + _variableLength);
  1180. if(_variableLength) {
  1181. rtn.append("\n\tCompressed Unicode: " + _compressedUnicode);
  1182. }
  1183. if(_autoNumber) {
  1184. rtn.append("\n\tLast AutoNumber: " + _autoNumberGenerator.getLast());
  1185. }
  1186. rtn.append("\n\n");
  1187. return rtn.toString();
  1188. }
  1189. /**
  1190. * @param textBytes bytes of text to decode
  1191. * @param format relevant db format
  1192. * @return the decoded string
  1193. */
  1194. public static String decodeUncompressedText(byte[] textBytes,
  1195. JetFormat format)
  1196. {
  1197. return decodeUncompressedText(textBytes, 0, textBytes.length, format)
  1198. .toString();
  1199. }
  1200. /**
  1201. * @param text Text to encode
  1202. * @param format relevant db format
  1203. * @return A buffer with the text encoded
  1204. */
  1205. public static ByteBuffer encodeUncompressedText(CharSequence text,
  1206. JetFormat format)
  1207. {
  1208. return format.CHARSET.encode(CharBuffer.wrap(text));
  1209. }
  1210. public int compareTo(Column other) {
  1211. if (_columnNumber > other.getColumnNumber()) {
  1212. return 1;
  1213. } else if (_columnNumber < other.getColumnNumber()) {
  1214. return -1;
  1215. } else {
  1216. return 0;
  1217. }
  1218. }
  1219. /**
  1220. * @param columns A list of columns in a table definition
  1221. * @return The number of variable length columns found in the list
  1222. */
  1223. public static short countVariableLength(List<Column> columns) {
  1224. short rtn = 0;
  1225. for (Column col : columns) {
  1226. if (col.isVariableLength()) {
  1227. rtn++;
  1228. }
  1229. }
  1230. return rtn;
  1231. }
  1232. /**
  1233. * @param columns A list of columns in a table definition
  1234. * @return The number of variable length columns which are not long values
  1235. * found in the list
  1236. */
  1237. public static short countNonLongVariableLength(List<Column> columns) {
  1238. short rtn = 0;
  1239. for (Column col : columns) {
  1240. if (col.isVariableLength() && !col.getType().isLongValue()) {
  1241. rtn++;
  1242. }
  1243. }
  1244. return rtn;
  1245. }
  1246. /**
  1247. * @return an appropriate BigDecimal representation of the given object.
  1248. * <code>null</code> is returned as 0 and Numbers are converted
  1249. * using their double representation.
  1250. */
  1251. private static BigDecimal toBigDecimal(Object value)
  1252. {
  1253. if(value == null) {
  1254. return BigDecimal.ZERO;
  1255. } else if(value instanceof BigDecimal) {
  1256. return (BigDecimal)value;
  1257. } else if(value instanceof BigInteger) {
  1258. return new BigDecimal((BigInteger)value);
  1259. } else if(value instanceof Number) {
  1260. return new BigDecimal(((Number)value).doubleValue());
  1261. }
  1262. return new BigDecimal(value.toString());
  1263. }
  1264. /**
  1265. * @return an appropriate Number representation of the given object.
  1266. * <code>null</code> is returned as 0 and Strings are parsed as
  1267. * Doubles.
  1268. */
  1269. private static Number toNumber(Object value)
  1270. {
  1271. if(value == null) {
  1272. return BigDecimal.ZERO;
  1273. } if(value instanceof Number) {
  1274. return (Number)value;
  1275. }
  1276. return Double.valueOf(value.toString());
  1277. }
  1278. /**
  1279. * @return an appropriate CharSequence representation of the given object.
  1280. */
  1281. public static CharSequence toCharSequence(Object value)
  1282. throws IOException
  1283. {
  1284. if(value == null) {
  1285. return null;
  1286. } else if(value instanceof CharSequence) {
  1287. return (CharSequence)value;
  1288. } else if(value instanceof Clob) {
  1289. try {
  1290. Clob c = (Clob)value;
  1291. // note, start pos is 1-based
  1292. return c.getSubString(1L, (int)c.length());
  1293. } catch(SQLException e) {
  1294. throw (IOException)(new IOException(e.getMessage())).initCause(e);
  1295. }
  1296. } else if(value instanceof Reader) {
  1297. char[] buf = new char[8 * 1024];
  1298. StringBuilder sout = new StringBuilder();
  1299. Reader in = (Reader)value;
  1300. int read = 0;
  1301. while((read = in.read(buf)) != -1) {
  1302. sout.append(buf, 0, read);
  1303. }
  1304. return sout;
  1305. }
  1306. return value.toString();
  1307. }
  1308. /**
  1309. * @return an appropriate byte[] representation of the given object.
  1310. */
  1311. public static byte[] toByteArray(Object value)
  1312. throws IOException
  1313. {
  1314. if(value == null) {
  1315. return null;
  1316. } else if(value instanceof byte[]) {
  1317. return (byte[])value;
  1318. } else if(value instanceof Blob) {
  1319. try {
  1320. Blob b = (Blob)value;
  1321. // note, start pos is 1-based
  1322. return b.getBytes(1L, (int)b.length());
  1323. } catch(SQLException e) {
  1324. throw (IOException)(new IOException(e.getMessage())).initCause(e);
  1325. }
  1326. }
  1327. ByteArrayOutputStream bout = new ByteArrayOutputStream();
  1328. if(value instanceof InputStream) {
  1329. byte[] buf = new byte[8 * 1024];
  1330. InputStream in = (InputStream)value;
  1331. int read = 0;
  1332. while((read = in.read(buf)) != -1) {
  1333. bout.write(buf, 0, read);
  1334. }
  1335. } else {
  1336. // if all else fails, serialize it
  1337. ObjectOutputStream oos = new ObjectOutputStream(bout);
  1338. oos.writeObject(value);
  1339. oos.close();
  1340. }
  1341. return bout.toByteArray();
  1342. }
  1343. /**
  1344. * Interpret a boolean value (null == false)
  1345. */
  1346. public static boolean toBooleanValue(Object obj) {
  1347. return ((obj != null) && ((Boolean)obj).booleanValue());
  1348. }
  1349. /**
  1350. * Swaps the bytes of the given numeric in place.
  1351. */
  1352. private static void fixNumericByteOrder(byte[] bytes)
  1353. {
  1354. // fix endianness of each 4 byte segment
  1355. for(int i = 0; i < 4; ++i) {
  1356. ByteUtil.swap4Bytes(bytes, i * 4);
  1357. }
  1358. }
  1359. /**
  1360. * Treat booleans as integers (C-style).
  1361. */
  1362. private static Object booleanToInteger(Object obj) {
  1363. if (obj instanceof Boolean) {
  1364. obj = ((Boolean) obj) ? 1 : 0;
  1365. }
  1366. return obj;
  1367. }
  1368. /**
  1369. * Returns a wrapper for raw column data that can be written without
  1370. * understanding the data. Useful for wrapping unparseable data for
  1371. * re-writing.
  1372. */
  1373. static RawData rawDataWrapper(byte[] bytes) {
  1374. return new RawData(bytes);
  1375. }
  1376. /**
  1377. * Returs {@code true} if the given value is "raw" column data,
  1378. * {@code false} otherwise.
  1379. */
  1380. static boolean isRawData(Object value) {
  1381. return(value instanceof RawData);
  1382. }
  1383. /**
  1384. * Date subclass which stashes the original date bits, in case we attempt to
  1385. * re-write the value (will not lose precision).
  1386. */
  1387. private static final class DateExt extends Date
  1388. {
  1389. private static final long serialVersionUID = 0L;
  1390. /** cached bits of the original date value */
  1391. private transient final long _dateBits;
  1392. private DateExt(long time, long dateBits) {
  1393. super(time);
  1394. _dateBits = dateBits;
  1395. }
  1396. public long getDateBits() {
  1397. return _dateBits;
  1398. }
  1399. private Object writeReplace() throws ObjectStreamException {
  1400. // if we are going to serialize this Date, convert it back to a normal
  1401. // Date (in case it is restored outside of the context of jackcess)
  1402. return new Date(super.getTime());
  1403. }
  1404. }
  1405. /**
  1406. * Wrapper for raw column data which can be re-written.
  1407. */
  1408. private static class RawData implements Serializable
  1409. {
  1410. private static final long serialVersionUID = 0L;
  1411. private final byte[] _bytes;
  1412. private RawData(byte[] bytes) {
  1413. _bytes = bytes;
  1414. }
  1415. private byte[] getBytes() {
  1416. return _bytes;
  1417. }
  1418. @Override
  1419. public String toString() {
  1420. return "RawData: " + ByteUtil.toHexString(getBytes());
  1421. }
  1422. private Object writeReplace() throws ObjectStreamException {
  1423. // if we are going to serialize this, convert it back to a normal
  1424. // byte[] (in case it is restored outside of the context of jackcess)
  1425. return getBytes();
  1426. }
  1427. }
  1428. /**
  1429. * Base class for the supported autonumber types.
  1430. */
  1431. public abstract class AutoNumberGenerator
  1432. {
  1433. protected AutoNumberGenerator() {}
  1434. /**
  1435. * Returns the last autonumber generated by this generator. Only valid
  1436. * after a call to {@link Table#addRow}, otherwise undefined.
  1437. */
  1438. public abstract Object getLast();
  1439. /**
  1440. * Returns the next autonumber for this generator.
  1441. * <p>
  1442. * <i>Warning, calling this externally will result in this value being
  1443. * "lost" for the table.</i>
  1444. */
  1445. public abstract Object getNext();
  1446. /**
  1447. * Returns the flags used when writing this column.
  1448. */
  1449. public abstract int getColumnFlags();
  1450. /**
  1451. * Returns the type of values generated by this generator.
  1452. */
  1453. public abstract DataType getType();
  1454. }
  1455. private final class LongAutoNumberGenerator extends AutoNumberGenerator
  1456. {
  1457. private LongAutoNumberGenerator() {}
  1458. @Override
  1459. public Object getLast() {
  1460. // the table stores the last long autonumber used
  1461. return getTable().getLastLongAutoNumber();
  1462. }
  1463. @Override
  1464. public Object getNext() {
  1465. // the table stores the last long autonumber used
  1466. return getTable().getNextLongAutoNumber();
  1467. }
  1468. @Override
  1469. public int getColumnFlags() {
  1470. return AUTO_NUMBER_FLAG_MASK;
  1471. }
  1472. @Override
  1473. public DataType getType() {
  1474. return DataType.LONG;
  1475. }
  1476. }
  1477. private final class GuidAutoNumberGenerator extends AutoNumberGenerator
  1478. {
  1479. private Object _lastAutoNumber;
  1480. private GuidAutoNumberGenerator() {}
  1481. @Override
  1482. public Object getLast() {
  1483. return _lastAutoNumber;
  1484. }
  1485. @Override
  1486. public Object getNext() {
  1487. // format guids consistently w/ Column.readGUIDValue()
  1488. _lastAutoNumber = "{" + UUID.randomUUID() + "}";
  1489. return _lastAutoNumber;
  1490. }
  1491. @Override
  1492. public int getColumnFlags() {
  1493. return AUTO_NUMBER_GUID_FLAG_MASK;
  1494. }
  1495. @Override
  1496. public DataType getType() {
  1497. return DataType.GUID;
  1498. }
  1499. }
  1500. }