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 42KB

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