diff options
Diffstat (limited to 'src')
81 files changed, 1922 insertions, 1359 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/Cursor.java b/src/main/java/com/healthmarketscience/jackcess/Cursor.java index 7260b79..8503623 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Cursor.java +++ b/src/main/java/com/healthmarketscience/jackcess/Cursor.java @@ -31,7 +31,7 @@ import com.healthmarketscience.jackcess.util.IterableBuilder; * of table modification during traversal (although depending on how the table * is traversed, row updates may or may not be seen). Multiple cursors may * traverse the same table simultaneously. - * <p/> + * <br> * Basic cursors will generally iterate table data in the order it appears in * the database and searches will require scanning the entire table. * Additional features are available when utilizing an {@link Index} backed @@ -135,7 +135,7 @@ public interface Cursor extends Iterable<Row> * Iterator which will iterate through all the rows of this table. Use of * the Iterator follows the same restrictions as a call to * {@link #getNextRow}. - * <p/> + * <br> * For more flexible iteration see {@link #newIterable}. * @throws RuntimeIOException if an IOException is thrown by one of the * operations, the actual exception will be contained within @@ -151,7 +151,7 @@ public interface Cursor extends Iterable<Row> /** * Delete the current row. - * <p/> + * <br> * Note, re-deleting an already deleted row is allowed (it does nothing). * @throws IllegalStateException if the current row is not valid (at * beginning or end of table) @@ -178,7 +178,7 @@ public interface Cursor extends Iterable<Row> /** * Moves to the next row in the table and returns it. - * @return The next row in this table (Column name -> Column value), or + * @return The next row in this table (Column name -> Column value), or * {@code null} if no next row is found */ public Row getNextRow() throws IOException; @@ -186,7 +186,7 @@ public interface Cursor extends Iterable<Row> /** * Moves to the next row in the table and returns it. * @param columnNames Only column names in this collection will be returned - * @return The next row in this table (Column name -> Column value), or + * @return The next row in this table (Column name -> Column value), or * {@code null} if no next row is found */ public Row getNextRow(Collection<String> columnNames) @@ -194,7 +194,7 @@ public interface Cursor extends Iterable<Row> /** * Moves to the previous row in the table and returns it. - * @return The previous row in this table (Column name -> Column value), or + * @return The previous row in this table (Column name -> Column value), or * {@code null} if no previous row is found */ public Row getPreviousRow() throws IOException; @@ -202,7 +202,7 @@ public interface Cursor extends Iterable<Row> /** * Moves to the previous row in the table and returns it. * @param columnNames Only column names in this collection will be returned - * @return The previous row in this table (Column name -> Column value), or + * @return The previous row in this table (Column name -> Column value), or * {@code null} if no previous row is found */ public Row getPreviousRow(Collection<String> columnNames) @@ -325,12 +325,12 @@ public interface Cursor extends Iterable<Row> public int movePreviousRows(int numRows) throws IOException; /** - * Returns the current row in this cursor (Column name -> Column value). + * Returns the current row in this cursor (Column name -> Column value). */ public Row getCurrentRow() throws IOException; /** - * Returns the current row in this cursor (Column name -> Column value). + * Returns the current row in this cursor (Column name -> Column value). * @param columnNames Only column names in this collection will be returned */ public Row getCurrentRow(Collection<String> columnNames) diff --git a/src/main/java/com/healthmarketscience/jackcess/CursorBuilder.java b/src/main/java/com/healthmarketscience/jackcess/CursorBuilder.java index 831c78f..3ae058b 100644 --- a/src/main/java/com/healthmarketscience/jackcess/CursorBuilder.java +++ b/src/main/java/com/healthmarketscience/jackcess/CursorBuilder.java @@ -34,14 +34,14 @@ import com.healthmarketscience.jackcess.util.ColumnMatcher; * Builder style class for constructing a {@link Cursor}. By default, a * cursor is created at the beginning of the table, and any start/end rows are * inclusive. - * <p/> + * <br> * Simple example traversal: * <pre> * for(Row row : table.newCursor().toCursor()) { * // ... process each row ... * } * </pre> - * <p/> + * <br> * Simple example search: * <pre> * Row row = CursorBuilder.findRow(table, Collections.singletonMap(col, "foo")); diff --git a/src/main/java/com/healthmarketscience/jackcess/DataType.java b/src/main/java/com/healthmarketscience/jackcess/DataType.java index 6850ab6..11483b1 100644 --- a/src/main/java/com/healthmarketscience/jackcess/DataType.java +++ b/src/main/java/com/healthmarketscience/jackcess/DataType.java @@ -24,13 +24,14 @@ import java.sql.Types; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.time.LocalDateTime; import com.healthmarketscience.jackcess.impl.DatabaseImpl; import com.healthmarketscience.jackcess.impl.JetFormat; /** * Supported access data types. - * + * * @author Tim McCune * @usage _general_class_ */ @@ -87,9 +88,10 @@ public enum DataType { */ DOUBLE((byte) 0x07, Types.DOUBLE, 8), /** - * Corresponds to a java {@link Date}. Accepts a Date, any {@link Number} - * (using {@link Number#longValue}), or {@code null}. Equivalent to SQL - * {@link Types#TIMESTAMP}, {@link Types#DATE}, {@link Types#TIME}. + * Corresponds to a java {@link Date} or {@link LocalDateTime}. Accepts a + * Date, LocalDateTime (or related types), any {@link Number} (using {@link + * Number#longValue}), or {@code null}. Equivalent to SQL {@link + * Types#TIMESTAMP}, {@link Types#DATE}, {@link Types#TIME}. */ SHORT_DATE_TIME((byte) 0x08, Types.TIMESTAMP, 8), /** @@ -104,7 +106,7 @@ public enum DataType { * null}. Equivalent to SQL {@link Types#VARCHAR}, {@link Types#CHAR}. */ TEXT((byte) 0x0A, Types.VARCHAR, null, true, false, 0, - JetFormat.TEXT_FIELD_MAX_LENGTH, JetFormat.TEXT_FIELD_MAX_LENGTH, + JetFormat.TEXT_FIELD_MAX_LENGTH, JetFormat.TEXT_FIELD_MAX_LENGTH, JetFormat.TEXT_FIELD_UNIT_SIZE), /** * Corresponds to a java {@code byte[]} of max length 16777215 bytes. @@ -151,7 +153,7 @@ public enum DataType { * Complex type corresponds to a special {@link #LONG} autonumber field * which is the key for a secondary table which holds the "real" data. */ - COMPLEX_TYPE((byte) 0x12, null, 4), + COMPLEX_TYPE((byte) 0x12, null, 4), /** * Corresponds to a java {@link Long}. Accepts any {@link Number} (using * {@link Number#longValue}), Boolean as 1 or 0, any Object converted to a @@ -206,7 +208,7 @@ public enum DataType { addNewSqlType("TIME_WITH_TIMEZONE", SHORT_DATE_TIME, null); addNewSqlType("TIMESTAMP_WITH_TIMEZONE", SHORT_DATE_TIME, null); } - + private static Map<Byte, DataType> DATA_TYPES = new HashMap<Byte, DataType>(); static { for (DataType type : DataType.values()) { @@ -249,11 +251,11 @@ public enum DataType { private final int _maxPrecision; /** the number of bytes per "unit" for this data type */ private final int _unitSize; - + private DataType(byte value) { this(value, null, null); } - + private DataType(byte value, Integer sqlType, Integer fixedSize) { this(value, sqlType, fixedSize, false, false, 0, 0, 0, 1); } @@ -269,7 +271,7 @@ public enum DataType { minSize, defaultSize, maxSize, false, 0, 0, 0, 0, 0, 0, unitSize); } - + private DataType(byte value, Integer sqlType, Integer fixedSize, boolean variableLength, boolean longValue, @@ -301,11 +303,11 @@ public enum DataType { _maxPrecision = maxPrecision; _unitSize = unitSize; } - + public byte getValue() { return _value; } - + public boolean isVariableLength() { return _variableLength; } @@ -315,7 +317,7 @@ public enum DataType { // e.g. NUMERIC return (isVariableLength() && (getMinSize() != getMaxSize())); } - + public boolean isLongValue() { return _longValue; } @@ -327,7 +329,7 @@ public enum DataType { public int getFixedSize() { return getFixedSize(null); } - + public int getFixedSize(Short colLength) { if(_fixedSize != null) { if(colLength != null) { @@ -338,7 +340,7 @@ public enum DataType { if(colLength != null) { return colLength; } - throw new IllegalArgumentException("Unexpected fixed length column " + + throw new IllegalArgumentException("Unexpected fixed length column " + this); } @@ -353,7 +355,7 @@ public enum DataType { public int getMaxSize() { return _maxSize; } - + public int getSQLType() throws SQLException { if (_sqlType != null) { return _sqlType; @@ -368,19 +370,19 @@ public enum DataType { public int getDefaultScale() { return _defaultScale; } - + public int getMaxScale() { return _maxScale; } - + public int getMinPrecision() { return _minPrecision; } - + public int getDefaultPrecision() { return _defaultPrecision; } - + public int getMaxPrecision() { return _maxPrecision; } @@ -414,7 +416,7 @@ public enum DataType { private static boolean isWithinRange(int value, int minValue, int maxValue) { return((value >= minValue) && (value <= maxValue)); } - + public int toValidSize(int size) { return toValidRange(size, getMinSize(), getMaxSize()); } @@ -442,12 +444,12 @@ public enum DataType { public boolean isUnsupported() { return((this == UNSUPPORTED_FIXEDLEN) || (this == UNSUPPORTED_VARLEN)); } - + private static int toValidRange(int value, int minValue, int maxValue) { return((value > maxValue) ? maxValue : ((value < minValue) ? minValue : value)); } - + public static DataType fromByte(byte b) throws IOException { DataType rtn = DATA_TYPES.get(b); if (rtn != null) { @@ -455,13 +457,13 @@ public enum DataType { } throw new IOException("Unrecognized data type: " + b); } - + public static DataType fromSQLType(int sqlType) throws SQLException { return fromSQLType(sqlType, 0, null); } - + public static DataType fromSQLType(int sqlType, int lengthInUnits) throws SQLException { @@ -504,7 +506,7 @@ public enum DataType { rtn = altRtn; } } - + return rtn; } @@ -512,7 +514,7 @@ public enum DataType { * Adds mappings for a sql type which was added after jdk 1.5 (using * reflection). */ - private static void addNewSqlType(String typeName, DataType type, + private static void addNewSqlType(String typeName, DataType type, DataType altType) { try { diff --git a/src/main/java/com/healthmarketscience/jackcess/Database.java b/src/main/java/com/healthmarketscience/jackcess/Database.java index d853fe8..3a7b65a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Database.java +++ b/src/main/java/com/healthmarketscience/jackcess/Database.java @@ -21,6 +21,8 @@ import java.io.File; import java.io.Flushable; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.file.Path; +import java.time.ZoneId; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; @@ -29,8 +31,8 @@ import java.util.Set; import java.util.TimeZone; import com.healthmarketscience.jackcess.expr.EvalConfig; -import com.healthmarketscience.jackcess.query.Query; import com.healthmarketscience.jackcess.impl.DatabaseImpl; +import com.healthmarketscience.jackcess.query.Query; import com.healthmarketscience.jackcess.util.ColumnValidatorFactory; import com.healthmarketscience.jackcess.util.ErrorHandler; import com.healthmarketscience.jackcess.util.LinkResolver; @@ -44,11 +46,11 @@ import com.healthmarketscience.jackcess.util.TableIterableBuilder; * Database has been opened, you can interact with the data via the relevant * {@link Table}. When a Database instance is no longer useful, it should * <b>always</b> be closed ({@link #close}) to avoid corruption. - * <p/> + * <br> * Database instances (and all the related objects) are <i>not</i> * thread-safe. However, separate Database instances (and their respective * objects) can be used by separate threads without a problem. - * <p/> + * <br> * Database instances do not implement any "transactional" support, and * therefore concurrent editing of the same database file by multiple Database * instances (or with outside programs such as MS Access) <i>will generally @@ -179,6 +181,11 @@ public interface Database extends Iterable<Table>, Closeable, Flushable public File getFile(); /** + * Returns the File underlying this Database + */ + public Path getPath(); + + /** * @return The names of all of the user tables * @usage _general_method_ */ @@ -201,6 +208,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable * database while an Iterator is in use. * @usage _general_method_ */ + @Override public Iterator<Table> iterator(); /** @@ -240,7 +248,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable /** * Finds all the relationships in the database in <i>non-system</i> tables. - * </p> + * <br> * Warning, this may load <i>all</i> the Tables (metadata, not data) in the * database which could cause memory issues. * @usage _intermediate_method_ @@ -250,7 +258,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable /** * Finds <i>all</i> the relationships in the database, <i>including system * tables</i>. - * </p> + * <br> * Warning, this may load <i>all</i> the Tables (metadata, not data) in the * database which could cause memory issues. * @usage _intermediate_method_ @@ -319,6 +327,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable * databases) to disk. * @usage _general_method_ */ + @Override public void flush() throws IOException; /** @@ -329,6 +338,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable * OutputStream or jdbc Connection). * @usage _general_method_ */ + @Override public void close() throws IOException; /** @@ -377,18 +387,34 @@ public interface Database extends Iterable<Table>, Closeable, Flushable public boolean isLinkedTable(Table table) throws IOException; /** - * Gets currently configured TimeZone (always non-{@code null}). + * Gets currently configured TimeZone (always non-{@code null} and aligned + * with the ZoneId). * @usage _intermediate_method_ */ public TimeZone getTimeZone(); /** - * Sets a new TimeZone. If {@code null}, resets to the default value. + * Sets a new TimeZone. If {@code null}, resets to the default value. Note + * that setting the TimeZone will alter the ZoneId as well. * @usage _intermediate_method_ */ public void setTimeZone(TimeZone newTimeZone); /** + * Gets currently configured ZoneId (always non-{@code null} and aligned + * with the TimeZone). + * @usage _intermediate_method_ + */ + public ZoneId getZoneId(); + + /** + * Sets a new ZoneId. If {@code null}, resets to the default value. Note + * that setting the ZoneId will alter the TimeZone as well. + * @usage _intermediate_method_ + */ + public void setZoneId(ZoneId newZoneId); + + /** * Gets currently configured Charset (always non-{@code null}). * @usage _intermediate_method_ */ @@ -492,4 +518,16 @@ public interface Database extends Iterable<Table>, Closeable, Flushable * Returns the EvalConfig for configuring expression evaluation. */ public EvalConfig getEvalConfig(); + + /** + * Gets the currently configured DateTimeType. + * @usage _general_method_ + */ + public DateTimeType getDateTimeType(); + + /** + * Sets the DateTimeType. If {@code null}, resets to the default value. + * @usage _general_method_ + */ + public void setDateTimeType(DateTimeType dateTimeType); } diff --git a/src/main/java/com/healthmarketscience/jackcess/DatabaseBuilder.java b/src/main/java/com/healthmarketscience/jackcess/DatabaseBuilder.java index c5e0252..dce1901 100644 --- a/src/main/java/com/healthmarketscience/jackcess/DatabaseBuilder.java +++ b/src/main/java/com/healthmarketscience/jackcess/DatabaseBuilder.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.charset.Charset; +import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; @@ -35,12 +36,12 @@ import com.healthmarketscience.jackcess.util.MemFileChannel; /** * Builder style class for opening/creating a {@link Database}. - * <p/> + * <br> * Simple example usage: * <pre> * Database db = DatabaseBuilder.open(new File("test.mdb")); * </pre> - * <p/> + * <br> * Advanced example usage: * <pre> * Database db = new DatabaseBuilder(new File("test.mdb")) @@ -51,10 +52,10 @@ import com.healthmarketscience.jackcess.util.MemFileChannel; * @author James Ahlborn * @usage _general_class_ */ -public class DatabaseBuilder +public class DatabaseBuilder { /** the file name of the mdb to open/create */ - private File _mdbFile; + private Path _mdbFile; /** whether or not to open existing mdb read-only */ private boolean _readOnly; /** whether or not to auto-sync writes to the filesystem */ @@ -77,12 +78,16 @@ public class DatabaseBuilder /** database user-defined (if any) */ private Map<String,PropertyMap.Property> _userProps; - + public DatabaseBuilder() { - this(null); + this((Path)null); } public DatabaseBuilder(File mdbFile) { + this(toPath(mdbFile)); + } + + public DatabaseBuilder(Path mdbFile) { _mdbFile = mdbFile; } @@ -93,6 +98,16 @@ public class DatabaseBuilder * @usage _general_method_ */ public DatabaseBuilder setFile(File mdbFile) { + return setPath(toPath(mdbFile)); + } + + /** + * File containing an existing database for {@link #open} or target file for + * new database for {@link #create} (in which case, <b>tf this file already + * exists, it will be overwritten.</b>) + * @usage _general_method_ + */ + public DatabaseBuilder setPath(Path mdbFile) { _mdbFile = mdbFile; return this; } @@ -183,7 +198,7 @@ public class DatabaseBuilder public DatabaseBuilder putDatabaseProperty(String name, Object value) { return putDatabaseProperty(name, null, value); } - + /** * Sets the database property with the given name and type to the given * value. @@ -193,7 +208,7 @@ public class DatabaseBuilder _dbProps = putProperty(_dbProps, name, type, value); return this; } - + /** * Sets the summary database property with the given name to the given * value. Attempts to determine the type of the property (see @@ -203,7 +218,7 @@ public class DatabaseBuilder public DatabaseBuilder putSummaryProperty(String name, Object value) { return putSummaryProperty(name, null, value); } - + /** * Sets the summary database property with the given name and type to * the given value. @@ -223,7 +238,7 @@ public class DatabaseBuilder public DatabaseBuilder putUserDefinedProperty(String name, Object value) { return putUserDefinedProperty(name, null, value); } - + /** * Sets the user-defined database property with the given name and type to * the given value. @@ -257,7 +272,7 @@ public class DatabaseBuilder * Creates a new Database using the configured information. */ public Database create() throws IOException { - Database db = DatabaseImpl.create(_fileFormat, _mdbFile, _channel, _autoSync, + Database db = DatabaseImpl.create(_fileFormat, _mdbFile, _channel, _autoSync, _charset, _timeZone); if(_dbProps != null) { PropertyMap props = db.getDatabaseProperties(); @@ -281,19 +296,19 @@ public class DatabaseBuilder * Open an existing Database. If the existing file is not writeable, the * file will be opened read-only. Auto-syncing is enabled for the returned * Database. - * + * * @param mdbFile File containing the database - * + * * @see DatabaseBuilder for more flexible Database opening * @usage _general_method_ */ public static Database open(File mdbFile) throws IOException { return new DatabaseBuilder(mdbFile).open(); } - + /** * Create a new Database for the given fileFormat - * + * * @param fileFormat version of new database. * @param mdbFile Location to write the new database to. <b>If this file * already exists, it will be overwritten.</b> @@ -301,8 +316,8 @@ public class DatabaseBuilder * @see DatabaseBuilder for more flexible Database creation * @usage _general_method_ */ - public static Database create(Database.FileFormat fileFormat, File mdbFile) - throws IOException + public static Database create(Database.FileFormat fileFormat, File mdbFile) + throws IOException { return new DatabaseBuilder(mdbFile).setFileFormat(fileFormat).create(); } @@ -330,4 +345,8 @@ public class DatabaseBuilder } return cal; } + + private static Path toPath(File file) { + return ((file != null) ? file.toPath() : null); + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/DateTimeType.java b/src/main/java/com/healthmarketscience/jackcess/DateTimeType.java new file mode 100644 index 0000000..8704350 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/DateTimeType.java @@ -0,0 +1,33 @@ +/* +Copyright (c) 2018 James Ahlborn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.healthmarketscience.jackcess; + +/** + * Enum for selecting how a Database returns date/time types. Prefer using + * {@link DateTimeType#LOCAL_DATE_TIME} as using Date is being phased out and + * will eventually be removed. + * + * @author James Ahlborn + */ +public enum DateTimeType +{ + /** use legacy {@link java.util.Date} objects. To maintain backwards + compatibility, this is the default type. */ + DATE, + /** use jdk8+ {@link java.time.LocalDateTime} objects */ + LOCAL_DATE_TIME; +} diff --git a/src/main/java/com/healthmarketscience/jackcess/IndexCursor.java b/src/main/java/com/healthmarketscience/jackcess/IndexCursor.java index 1e4aa34..a48a2ce 100644 --- a/src/main/java/com/healthmarketscience/jackcess/IndexCursor.java +++ b/src/main/java/com/healthmarketscience/jackcess/IndexCursor.java @@ -41,9 +41,9 @@ public interface IndexCursor extends Cursor * @param entryValues the column values for the index's columns. * @return the matching row or {@code null} if a match could not be found. */ - public Row findRowByEntry(Object... entryValues) + public Row findRowByEntry(Object... entryValues) throws IOException; - + /** * Moves to the first row (as defined by the cursor) where the index entries * match the given values. If a match is not found (or an exception is @@ -56,32 +56,32 @@ public interface IndexCursor extends Cursor * @return {@code true} if a valid row was found with the given values, * {@code false} if no row was found */ - public boolean findFirstRowByEntry(Object... entryValues) + public boolean findFirstRowByEntry(Object... entryValues) throws IOException; /** * Moves to the first row (as defined by the cursor) where the index entries - * are >= the given values. If a an exception is thrown, the cursor is + * are >= the given values. If a an exception is thrown, the cursor is * restored to its previous state. * * @param entryValues the column values for the index's columns. */ - public void findClosestRowByEntry(Object... entryValues) + public void findClosestRowByEntry(Object... entryValues) throws IOException; /** * Returns {@code true} if the current row matches the given index entries. - * + * * @param entryValues the column values for the index's columns. */ - public boolean currentRowMatchesEntry(Object... entryValues) + public boolean currentRowMatchesEntry(Object... entryValues) throws IOException; /** * Convenience method for constructing a new EntryIterableBuilder for this * cursor. An EntryIterableBuilder provides a variety of options for more * flexible iteration based on a specific index entry. - * + * * @param entryValues the column values for the index's columns. */ public EntryIterableBuilder newEntryIterable(Object... entryValues); diff --git a/src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java b/src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java index adffc0f..2e161d2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java +++ b/src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java @@ -29,4 +29,8 @@ public class InvalidValueException extends JackcessException public InvalidValueException(String msg) { super(msg); } + + public InvalidValueException(String msg, Throwable cause) { + super(msg, cause); + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/PropertyMap.java b/src/main/java/com/healthmarketscience/jackcess/PropertyMap.java index 516a098..c8c8703 100644 --- a/src/main/java/com/healthmarketscience/jackcess/PropertyMap.java +++ b/src/main/java/com/healthmarketscience/jackcess/PropertyMap.java @@ -80,7 +80,7 @@ public interface PropertyMap extends Iterable<PropertyMap.Property> * determine the type of the property based on the name and value (the * property names listed above have their types builtin, otherwise the type * of the value is used). - * <p/> + * <br> * Note, this change will not be persisted until the {@link #save} method * has been called. * @@ -92,7 +92,7 @@ public interface PropertyMap extends Iterable<PropertyMap.Property> /** * Creates a new (or updates an existing) property in the map. - * <p/> + * <br> * Note, this change will not be persisted until the {@link #save} method * has been called. * @@ -102,7 +102,7 @@ public interface PropertyMap extends Iterable<PropertyMap.Property> /** * Creates a new (or updates an existing) property in the map. - * <p/> + * <br> * Note, this change will not be persisted until the {@link #save} method * has been called. * @@ -151,7 +151,7 @@ public interface PropertyMap extends Iterable<PropertyMap.Property> /** * Sets the new value for this property. - * <p/> + * <br> * Note, this change will not be persisted until the {@link * PropertyMap#save} method has been called. */ diff --git a/src/main/java/com/healthmarketscience/jackcess/RelationshipBuilder.java b/src/main/java/com/healthmarketscience/jackcess/RelationshipBuilder.java index 3ca3e85..d183de0 100644 --- a/src/main/java/com/healthmarketscience/jackcess/RelationshipBuilder.java +++ b/src/main/java/com/healthmarketscience/jackcess/RelationshipBuilder.java @@ -32,7 +32,7 @@ import com.healthmarketscience.jackcess.impl.RelationshipImpl; * if integrity enforcement is enabled, there must already be a unique index * on the "from" Table for the relevant columns (same requirement as MS * Access). - * <p/> + * <br> * Example: * <pre> * Relationship rel = new RelationshipBuilder("FromTable", "ToTable") diff --git a/src/main/java/com/healthmarketscience/jackcess/Row.java b/src/main/java/com/healthmarketscience/jackcess/Row.java index a599c20..8fcaf67 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Row.java +++ b/src/main/java/com/healthmarketscience/jackcess/Row.java @@ -20,13 +20,14 @@ import java.io.IOException; import java.util.Date; import java.util.Map; import java.math.BigDecimal; +import java.time.LocalDateTime; import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey; import com.healthmarketscience.jackcess.util.OleBlob; /** - * A row of data as column name->value pairs. Values are strongly typed, and + * A row of data as column name->value pairs. Values are strongly typed, and * column names are case sensitive. * * @author James Ahlborn @@ -35,7 +36,7 @@ import com.healthmarketscience.jackcess.util.OleBlob; public interface Row extends Map<String,Object> { /** - * @return the id of this row + * @return the id of this row */ public RowId getId(); @@ -90,11 +91,25 @@ public interface Row extends Map<String,Object> /** * Convenience method which gets the value for the row with the given name, * casting it to a Date (DataType SHORT_DATE_TIME). + * @deprecated this is only valid for Database instances configured for the + * legacy {@link DateTimeType#DATE}. Prefer using + * {@link DateTimeType#LOCAL_DATE_TIME} and the corresponding + * {@link #getLocalDateTime} method. Using Date is being phased + * out and will eventually be removed. */ + @Deprecated public Date getDate(String name); /** * Convenience method which gets the value for the row with the given name, + * casting it to a LocalDateTime (DataType SHORT_DATE_TIME). This method + * will only work for Database instances configured for + * {@link DateTimeType#LOCAL_DATE_TIME}. + */ + public LocalDateTime getLocalDateTime(String name); + + /** + * Convenience method which gets the value for the row with the given name, * casting it to a byte[] (DataTypes BINARY, OLE). */ public byte[] getBytes(String name); @@ -108,7 +123,7 @@ public interface Row extends Map<String,Object> /** * Convenience method which gets the value for the row with the given name, * converting it to an {@link OleBlob} (DataTypes OLE). - * </p> + * <br> * Note, <i>the OleBlob should be closed after use</i>. */ public OleBlob getBlob(String name) throws IOException; diff --git a/src/main/java/com/healthmarketscience/jackcess/Table.java b/src/main/java/com/healthmarketscience/jackcess/Table.java index bbb6885..496f2ea 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Table.java +++ b/src/main/java/com/healthmarketscience/jackcess/Table.java @@ -33,7 +33,7 @@ import com.healthmarketscience.jackcess.util.ErrorHandler; * {@link TableBuilder}. The {@link com.healthmarketscience.jackcess.util.Joiner} utility can be used to traverse * table relationships (e.g. find rows in another table based on a foreign-key * relationship). - * <p/> + * <br> * A Table instance is not thread-safe (see {@link Database} for more * thread-safety details). * @@ -160,14 +160,14 @@ public interface Table extends Iterable<Row> public Index getForeignKeyIndex(Table otherTable); /** - * Converts a map of columnName -> columnValue to an array of row values + * Converts a map of columnName -> columnValue to an array of row values * appropriate for a call to {@link #addRow(Object...)}. * @usage _general_method_ */ public Object[] asRow(Map<String,?> rowMap); /** - * Converts a map of columnName -> columnValue to an array of row values + * Converts a map of columnName -> columnValue to an array of row values * appropriate for a call to {@link Cursor#updateCurrentRow(Object...)}. * @usage _general_method_ */ @@ -203,7 +203,7 @@ public interface Table extends Iterable<Row> /** * Calls {@link #asRow} on the given row map and passes the result to {@link * #addRow}. - * <p/> + * <br> * Note, if this table has an auto-number column, the value generated will be * put back into the given row map. * @return the given row map, which will contain any autonumbers generated @@ -242,7 +242,7 @@ public interface Table extends Iterable<Row> /** * Calls {@link #asRow} on the given row maps and passes the results to * {@link #addRows}. - * <p/> + * <br> * Note, if this table has an auto-number column, the values generated will * be put back into the appropriate row maps. * <p> @@ -278,7 +278,7 @@ public interface Table extends Iterable<Row> * Iterator which will iterate through all the rows of this table. Use of * the Iterator follows the same restrictions as a call to * {@link #getNextRow}. - * <p/> + * <br> * For more advanced iteration, use the {@link #getDefaultCursor default * cursor} directly. * @throws RuntimeIOException if an IOException is thrown by one of the @@ -296,7 +296,7 @@ public interface Table extends Iterable<Row> public void reset(); /** - * @return The next row in this table (Column name -> Column value) (uses + * @return The next row in this table (Column name -> Column value) (uses * the {@link #getDefaultCursor default cursor}) * @usage _general_method_ */ diff --git a/src/main/java/com/healthmarketscience/jackcess/TableBuilder.java b/src/main/java/com/healthmarketscience/jackcess/TableBuilder.java index 31aa3a0..eaf6b1a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/TableBuilder.java +++ b/src/main/java/com/healthmarketscience/jackcess/TableBuilder.java @@ -32,7 +32,7 @@ import com.healthmarketscience.jackcess.impl.TableCreator; /** * Builder style class for constructing a {@link Table}. - * <p/> + * <br> * Example: * <pre> * Table table = new TableBuilder("Test") diff --git a/src/main/java/com/healthmarketscience/jackcess/complex/Attachment.java b/src/main/java/com/healthmarketscience/jackcess/complex/Attachment.java index d35559e..0047719 100644 --- a/src/main/java/com/healthmarketscience/jackcess/complex/Attachment.java +++ b/src/main/java/com/healthmarketscience/jackcess/complex/Attachment.java @@ -17,14 +17,16 @@ limitations under the License. package com.healthmarketscience.jackcess.complex; import java.io.IOException; +import java.time.LocalDateTime; import java.util.Date; +import com.healthmarketscience.jackcess.DateTimeType; /** * Complex value corresponding to an attachment. * * @author James Ahlborn */ -public interface Attachment extends ComplexValue +public interface Attachment extends ComplexValue { public byte[] getFileData() throws IOException; @@ -37,20 +39,34 @@ public interface Attachment extends ComplexValue public String getFileName(); public void setFileName(String fileName); - + public String getFileUrl(); public void setFileUrl(String fileUrl); - + public String getFileType(); public void setFileType(String fileType); - + + /** + * @deprecated see {@link DateTimeType} for details + */ + @Deprecated public Date getFileTimeStamp(); + /** + * @deprecated see {@link DateTimeType} for details + */ + @Deprecated public void setFileTimeStamp(Date fileTimeStamp); - + + public LocalDateTime getFileLocalTimeStamp(); + + public void setFileLocalTimeStamp(LocalDateTime fileTimeStamp); + + public Object getFileTimeStampObject(); + public Integer getFileFlags(); - public void setFileFlags(Integer fileFlags); + public void setFileFlags(Integer fileFlags); } diff --git a/src/main/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java b/src/main/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java index 150dd07..0e0bc13 100644 --- a/src/main/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java +++ b/src/main/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java @@ -18,10 +18,13 @@ package com.healthmarketscience.jackcess.complex; import java.io.IOException; import java.io.ObjectStreamException; +import java.time.LocalDateTime; import java.util.Date; import java.util.List; import java.util.Map; + import com.healthmarketscience.jackcess.Column; +import com.healthmarketscience.jackcess.DateTimeType; /** @@ -40,33 +43,33 @@ import com.healthmarketscience.jackcess.Column; */ public abstract class ComplexValueForeignKey extends Number { - private static final long serialVersionUID = 20130319L; + private static final long serialVersionUID = 20130319L; @Override public byte byteValue() { return (byte)get(); } - + @Override public short shortValue() { return (short)get(); } - + @Override public int intValue() { return get(); } - + @Override public long longValue() { return get(); } - + @Override public float floatValue() { return get(); } - + @Override public double doubleValue() { return get(); @@ -78,12 +81,12 @@ public abstract class ComplexValueForeignKey extends Number // of jackcess) return Integer.valueOf(get()); } - + @Override public int hashCode() { return get(); } - + @Override public boolean equals(Object o) { return ((this == o) || @@ -94,7 +97,7 @@ public abstract class ComplexValueForeignKey extends Number @Override public String toString() { return String.valueOf(get()); - } + } public abstract int get(); @@ -122,25 +125,50 @@ public abstract class ComplexValueForeignKey extends Number public abstract Version addVersion(String value) throws IOException; + /** + * @deprecated see {@link DateTimeType} for details + */ + @Deprecated public abstract Version addVersion(String value, Date modifiedDate) throws IOException; + public abstract Version addVersion(String value, LocalDateTime modifiedDate) + throws IOException; + public abstract Attachment addAttachment(byte[] data) throws IOException; + /** + * @deprecated see {@link DateTimeType} for details + */ + @Deprecated public abstract Attachment addAttachment( String url, String name, String type, byte[] data, Date timeStamp, Integer flags) throws IOException; + public abstract Attachment addAttachment( + String url, String name, String type, byte[] data, + LocalDateTime timeStamp, Integer flags) + throws IOException; + public abstract Attachment addEncodedAttachment(byte[] encodedData) throws IOException; + /** + * @deprecated see {@link DateTimeType} for details + */ + @Deprecated public abstract Attachment addEncodedAttachment( String url, String name, String type, byte[] encodedData, Date timeStamp, Integer flags) throws IOException; + public abstract Attachment addEncodedAttachment( + String url, String name, String type, byte[] encodedData, + LocalDateTime timeStamp, Integer flags) + throws IOException; + public abstract Attachment updateAttachment(Attachment attachment) throws IOException; diff --git a/src/main/java/com/healthmarketscience/jackcess/complex/Version.java b/src/main/java/com/healthmarketscience/jackcess/complex/Version.java index a1ace1b..374e047 100644 --- a/src/main/java/com/healthmarketscience/jackcess/complex/Version.java +++ b/src/main/java/com/healthmarketscience/jackcess/complex/Version.java @@ -16,7 +16,9 @@ limitations under the License. package com.healthmarketscience.jackcess.complex; +import java.time.LocalDateTime; import java.util.Date; +import com.healthmarketscience.jackcess.DateTimeType; /** * Complex value corresponding to a version of a memo column. @@ -27,5 +29,13 @@ public interface Version extends ComplexValue, Comparable<Version> { public String getValue(); + /** + * @deprecated see {@link DateTimeType} for details + */ + @Deprecated public Date getModifiedDate(); + + public LocalDateTime getModifiedLocalDate(); + + public Object getModifiedDateObject(); } diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/Identifier.java b/src/main/java/com/healthmarketscience/jackcess/expr/Identifier.java index 45db1ad..709a7cd 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/Identifier.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/Identifier.java @@ -16,7 +16,8 @@ limitations under the License. package com.healthmarketscience.jackcess.expr; -import org.apache.commons.lang.ObjectUtils; +import java.util.Objects; + /** * identifies a database entity (e.g. the name of a database field). An @@ -71,9 +72,9 @@ public class Identifier Identifier oi = (Identifier)o; - return (ObjectUtils.equals(_objectName, oi._objectName) && - ObjectUtils.equals(_collectionName, oi._collectionName) && - ObjectUtils.equals(_propertyName, oi._propertyName)); + return (Objects.equals(_objectName, oi._objectName) && + Objects.equals(_collectionName, oi._collectionName) && + Objects.equals(_propertyName, oi._propertyName)); } @Override diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java b/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java index a90a80b..7b7a306 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java @@ -17,8 +17,8 @@ limitations under the License. package com.healthmarketscience.jackcess.expr; import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; /** * LocaleContext encapsulates all shared localization state for expression @@ -35,16 +35,15 @@ public interface LocaleContext public TemporalConfig getTemporalConfig(); /** - * @return an appropriately configured (i.e. TimeZone and other date/time - * flags) SimpleDateFormat for the given format. + * @return an appropriately configured (i.e. locale) DateTimeFormatter for + * the given format. */ - public SimpleDateFormat createDateFormat(String formatStr); + public DateTimeFormatter createDateFormatter(String formatStr); /** - * @return an appropriately configured (i.e. TimeZone and other date/time - * flags) Calendar. + * @return the currently configured ZoneId */ - public Calendar getCalendar(); + public ZoneId getZoneId(); /** * @return the currently configured NumericConfig (from the diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java b/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java index db7806f..cfe08e1 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java @@ -30,19 +30,18 @@ import java.util.Locale; */ public class TemporalConfig { - public static final String US_DATE_FORMAT = "M/d/yyyy"; - public static final String US_DATE_IMPLICIT_YEAR_FORMAT = "M/d"; + public static final String US_DATE_FORMAT = "M/d[/uuuu]"; public static final String US_TIME_FORMAT_12_FORMAT = "h:mm:ss a"; public static final String US_TIME_FORMAT_24_FORMAT = "H:mm:ss"; - public static final String US_LONG_DATE_FORMAT = "EEEE, MMMM dd, yyyy"; + public static final String US_LONG_DATE_FORMAT = "EEEE, MMMM dd, uuuu"; - public static final String MEDIUM_DATE_FORMAT = "dd-MMM-yy"; + public static final String MEDIUM_DATE_FORMAT = "dd-MMM-uu"; public static final String MEDIUM_TIME_FORMAT = "hh:mm a"; public static final String SHORT_TIME_FORMAT = "HH:mm"; /** default implementation which is configured for the US locale */ public static final TemporalConfig US_TEMPORAL_CONFIG = new TemporalConfig( - US_DATE_FORMAT, US_DATE_IMPLICIT_YEAR_FORMAT, US_LONG_DATE_FORMAT, + US_DATE_FORMAT, US_LONG_DATE_FORMAT, US_TIME_FORMAT_12_FORMAT, US_TIME_FORMAT_24_FORMAT, '/', ':', Locale.US); public enum Type { @@ -133,8 +132,8 @@ public class TemporalConfig } } + private final Locale _locale; private final String _dateFormat; - private final String _dateImplicitYearFormat; private final String _longDateFormat; private final String _timeFormat12; private final String _timeFormat24; @@ -142,16 +141,15 @@ public class TemporalConfig private final char _timeSeparator; private final String _dateTimeFormat12; private final String _dateTimeFormat24; - private final DateFormatSymbols _symbols; + private final String[] _amPmStrings; /** * Instantiates a new TemporalConfig with the given configuration. Note * that the date/time format variants will be created by concatenating the - * relevant date and time formats, separated by a single space, e.g. "<date> - * <time>". + * relevant date and time formats, separated by a single space, + * e.g. "<date> <time>". * * @param dateFormat the date (no time) format - * @param dateImplicitYearFormat the date (no time) with no year format * @param timeFormat12 the 12 hour time format * @param timeFormat24 the 24 hour time format * @param dateSeparator the primary separator used to separate elements in @@ -163,21 +161,26 @@ public class TemporalConfig * string. This value should differ from the * dateSeparator. */ - public TemporalConfig(String dateFormat, String dateImplicitYearFormat, - String longDateFormat, + public TemporalConfig(String dateFormat, String longDateFormat, String timeFormat12, String timeFormat24, char dateSeparator, char timeSeparator, Locale locale) { + _locale = locale; _dateFormat = dateFormat; - _dateImplicitYearFormat = dateImplicitYearFormat; _longDateFormat = longDateFormat; _timeFormat12 = timeFormat12; _timeFormat24 = timeFormat24; _dateSeparator = dateSeparator; _timeSeparator = timeSeparator; - _dateTimeFormat12 = _dateFormat + " " + _timeFormat12; - _dateTimeFormat24 = _dateFormat + " " + _timeFormat24; - _symbols = DateFormatSymbols.getInstance(locale); + _dateTimeFormat12 = toDateTimeFormat(_dateFormat, _timeFormat12); + _dateTimeFormat24 = toDateTimeFormat(_dateFormat, _timeFormat24); + // there doesn't seem to be a good/easy way to get this in new jave.time + // api, so just use old api + _amPmStrings = DateFormatSymbols.getInstance(locale).getAmPmStrings(); + } + + public Locale getLocale() { + return _locale; } public String getDateFormat() { @@ -252,24 +255,8 @@ public class TemporalConfig } } - public String getImplicitYearDateTimeFormat(Type type) { - switch(type) { - case DATE: - return _dateImplicitYearFormat; - case DATE_TIME: - return toDateTimeFormat(_dateImplicitYearFormat, getDefaultTimeFormat()); - case DATE_TIME_12: - return toDateTimeFormat(_dateImplicitYearFormat, getTimeFormat12()); - case DATE_TIME_24: - return toDateTimeFormat(_dateImplicitYearFormat, getTimeFormat24()); - default: - throw new IllegalArgumentException( - "the given format does not include a date " + type); - } - } - - public DateFormatSymbols getDateFormatSymbols() { - return _symbols; + public String[] getAmPmStrings() { + return _amPmStrings; } private static String toDateTimeFormat(String dateFormat, String timeFormat) { diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/Value.java b/src/main/java/com/healthmarketscience/jackcess/expr/Value.java index 118215e..ded758b 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/Value.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/Value.java @@ -17,7 +17,7 @@ limitations under the License. package com.healthmarketscience.jackcess.expr; import java.math.BigDecimal; -import java.util.Date; +import java.time.LocalDateTime; /** * Wrapper for a typed primitive value used within the expression evaluation @@ -97,9 +97,9 @@ public interface Value public String getAsString(LocaleContext ctx); /** - * @return this primitive value converted to a Date + * @return this primitive value converted to a LocalDateTime */ - public Date getAsDateTime(LocaleContext ctx); + public LocalDateTime getAsLocalDateTime(LocaleContext ctx); /** * Since date/time values have different types, it may be more convenient to diff --git a/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java index 6e4d5ab..99b9162 100644 --- a/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java +++ b/src/main/java/com/healthmarketscience/jackcess/expr/package-info.java @@ -20,13 +20,13 @@ limitations under the License. * but can be globally enabled via the system property * "com.healthmarketscience.jackcess.enableExpressionEvaluation" or * selectively enabled on a per database basis using {@link com.healthmarketscience.jackcess.Database#setEvaluateExpressions(Boolean)}. - * <p/> + * <br> * The expression evaluation engine implementation does its best to follow all * the warts and idiosyncracies of Access expression evaluation (both those * that are documented as well as those discovered through experimentation). * These include such things as value conversions, "Null" handling, rounding * rules, and implicit interpretations of expression in certain contexts. - * <p/> + * <br> * Expressions can be used in a number of different places within an Access * database. When enabled, Jackcess supports the following usage: * <ul> @@ -50,14 +50,14 @@ limitations under the License. * record validation rules will be run for the entire record before * update. Failures are handled in a similar manner.</li> * </ul> - * <p/> + * <br> * <h2>Supporting Classes</h2> - * <p/> + * <br> * The classes in this package make up the public api for expression handling * in Jackcess. They generally fall into two categories: - * <p/> + * <br> * <h3>General Use Classes</h3> - * <p/> + * <br> * <ul> * <li>{@link com.healthmarketscience.jackcess.expr.EvalConfig} allows for customization of the expression * evaluation context for a given {@link com.healthmarketscience.jackcess.Database} instance.</li> @@ -72,9 +72,9 @@ limitations under the License. * <li>{@link com.healthmarketscience.jackcess.expr.ParseException} wrapper exception thrown for failures which * occur during expression parsing.</li> * </ul> - * <p/> + * <br> * <h3>Advanced Use Classes</h3> - * <p/> + * <br> * <ul> * <li>{@link com.healthmarketscience.jackcess.expr.EvalContext} encapsulates all shared state for expression * parsing and evaluation.</li> @@ -86,9 +86,9 @@ limitations under the License. * database field).</li> * <li>{@link com.healthmarketscience.jackcess.expr.Value} represents a typed primitive value.</li> * </ul> - * <p/> + * <br> * <h2>Function Support</h2> - * <p/> + * <br> * Jackcess supports many of the standard Access functions. The following * tables list the (hopefully) current status of support built into Jackcess. * diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java index 0e52fa4..28785d1 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java @@ -19,8 +19,9 @@ package com.healthmarketscience.jackcess.impl; import java.io.IOException; import java.math.BigDecimal; import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.Date; import java.util.EnumMap; @@ -28,6 +29,7 @@ import java.util.Map; import javax.script.Bindings; import com.healthmarketscience.jackcess.DataType; +import com.healthmarketscience.jackcess.DateTimeType; import com.healthmarketscience.jackcess.JackcessException; import com.healthmarketscience.jackcess.expr.EvalContext; import com.healthmarketscience.jackcess.expr.EvalException; @@ -82,12 +84,12 @@ public abstract class BaseEvalContext implements EvalContext return _dbCtx.getTemporalConfig(); } - public SimpleDateFormat createDateFormat(String formatStr) { - return _dbCtx.createDateFormat(formatStr); + public DateTimeFormatter createDateFormatter(String formatStr) { + return _dbCtx.createDateFormatter(formatStr); } - public Calendar getCalendar() { - return _dbCtx.getCalendar(); + public ZoneId getZoneId() { + return _dbCtx.getZoneId(); } public NumericConfig getNumericConfig() { @@ -146,7 +148,10 @@ public abstract class BaseEvalContext implements EvalContext protected Value toValue(Object val, DataType dType) { try { - val = ColumnImpl.toInternalValue(dType, val, getDatabase()); + // expression engine always uses LocalDateTime, so force that date/time + // type + val = ColumnImpl.toInternalValue(dType, val, getDatabase(), + ColumnImpl.LDT_DATE_TIME_FACTORY); if(val == null) { return ValueSupport.NULL_VAL; } @@ -158,7 +163,7 @@ public abstract class BaseEvalContext implements EvalContext case DATE: case TIME: case DATE_TIME: - return ValueSupport.toValue(vType, (Date)val); + return ValueSupport.toValue(vType, (LocalDateTime)val); case LONG: Integer i = ((val instanceof Integer) ? (Integer)val : ((Number)val).intValue()); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java index fb76ad7..141f47f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java @@ -26,7 +26,7 @@ import com.healthmarketscience.jackcess.InvalidValueException; /** * Utility code for dealing with calculated columns. - * <p/> + * <br> * These are the currently possible calculated types: FLOAT, DOUBLE, INT, * LONG, BIG_INT, GUID, SHORT_DATE_TIME, MONEY, BOOLEAN, NUMERIC, TEXT, MEMO. * diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index 0016687..6ab88ab 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -32,11 +32,22 @@ import java.nio.charset.Charset; import java.sql.Blob; import java.sql.Clob; import java.sql.SQLException; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.TimeZone; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -44,6 +55,7 @@ import java.util.regex.Pattern; import com.healthmarketscience.jackcess.Column; import com.healthmarketscience.jackcess.ColumnBuilder; import com.healthmarketscience.jackcess.DataType; +import com.healthmarketscience.jackcess.DateTimeType; import com.healthmarketscience.jackcess.InvalidValueException; import com.healthmarketscience.jackcess.PropertyMap; import com.healthmarketscience.jackcess.Table; @@ -52,9 +64,10 @@ import com.healthmarketscience.jackcess.complex.ComplexValue; import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey; import com.healthmarketscience.jackcess.expr.Identifier; import com.healthmarketscience.jackcess.impl.complex.ComplexValueForeignKeyImpl; +import com.healthmarketscience.jackcess.impl.expr.NumberFormatter; import com.healthmarketscience.jackcess.util.ColumnValidator; import com.healthmarketscience.jackcess.util.SimpleColumnValidator; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -63,7 +76,8 @@ import org.apache.commons.logging.LogFactory; * @author Tim McCune * @usage _intermediate_class_ */ -public class ColumnImpl implements Column, Comparable<ColumnImpl> { +public class ColumnImpl implements Column, Comparable<ColumnImpl>, ZoneContext +{ protected static final Log LOG = LogFactory.getLog(ColumnImpl.class); @@ -79,8 +93,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { /** * Access stores numeric dates in days. Java stores them in milliseconds. */ - private static final long MILLISECONDS_PER_DAY = - (24L * 60L * 60L * 1000L); + private static final long MILLISECONDS_PER_DAY = (24L * 60L * 60L * 1000L); + private static final long SECONDS_PER_DAY = (24L * 60L * 60L); + private static final long NANOS_PER_SECOND = 1_000_000_000L; + private static final long NANOS_PER_MILLI = 1_000_000L; + private static final long MILLIS_PER_SECOND = 1000L; /** * Access starts counting dates at Dec 30, 1899 (note, this strange date @@ -91,6 +108,16 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { static final long MILLIS_BETWEEN_EPOCH_AND_1900 = 25569L * MILLISECONDS_PER_DAY; + public static final LocalDate BASE_LD = LocalDate.of(1899, 12, 30); + public static final LocalTime BASE_LT = LocalTime.of(0, 0); + public static final LocalDateTime BASE_LDT = LocalDateTime.of(BASE_LD, BASE_LT); + + private static final DateTimeFactory DEF_DATE_TIME_FACTORY = + new DefaultDateTimeFactory(); + + static final DateTimeFactory LDT_DATE_TIME_FACTORY = + new LDTDateTimeFactory(); + /** * mask for the fixed len bit * @usage _advanced_field_ @@ -332,10 +359,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { // base does nothing } + @Override public TableImpl getTable() { return _table; } + @Override public DatabaseImpl getDatabase() { return getTable().getDatabase(); } @@ -354,14 +383,17 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return getDatabase().getPageChannel(); } + @Override public String getName() { return _name; } + @Override public boolean isVariableLength() { return _variableLength; } + @Override public boolean isAutoNumber() { return _autoNumber; } @@ -373,6 +405,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return _columnNumber; } + @Override public int getColumnIndex() { return _columnIndex; } @@ -391,22 +424,27 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return _displayIndex; } + @Override public DataType getType() { return _type; } + @Override public int getSQLType() throws SQLException { return _type.getSQLType(); } + @Override public boolean isCompressedUnicode() { return false; } + @Override public byte getPrecision() { return (byte)getType().getDefaultPrecision(); } + @Override public byte getScale() { return (byte)getType().getDefaultScale(); } @@ -425,14 +463,17 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return 0; } + @Override public short getLength() { return _columnLength; } + @Override public short getLengthInUnits() { return (short)getType().toUnitSize(getLength()); } + @Override public boolean isCalculated() { return _calculated; } @@ -455,14 +496,26 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return getDatabase().getCharset(); } - protected Calendar getCalendar() { - return getDatabase().getCalendar(); + @Override + public TimeZone getTimeZone() { + return getDatabase().getTimeZone(); + } + + @Override + public ZoneId getZoneId() { + return getDatabase().getZoneId(); + } + + protected DateTimeFactory getDateTimeFactory() { + return getDatabase().getDateTimeFactory(); } + @Override public boolean isAppendOnly() { return (getVersionHistoryColumn() != null); } + @Override public ColumnImpl getVersionHistoryColumn() { return null; } @@ -482,10 +535,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { throw new UnsupportedOperationException(); } + @Override public boolean isHyperlink() { return false; } + @Override public ComplexColumnInfo<? extends ComplexValue> getComplexInfo() { return null; } @@ -582,12 +637,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { reloadPropertiesValidators(); } + @Override public ColumnValidator getColumnValidator() { // unwrap any "internal" validator return ((_validator instanceof InternalColumnValidator) ? ((InternalColumnValidator)_validator).getExternal() : _validator); } + @Override public void setColumnValidator(ColumnValidator newValidator) { if(isAutoNumber()) { @@ -648,6 +705,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return _autoNumberGenerator; } + @Override public PropertyMap getProperties() throws IOException { if(_props == null) { _props = getTable().getPropertyMaps().get(getName()); @@ -655,20 +713,24 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return _props; } + @Override public Object setRowValue(Object[] rowArray, Object value) { rowArray[_columnIndex] = value; return value; } + @Override public Object setRowValue(Map<String,Object> rowMap, Object value) { rowMap.put(_name, value); return value; } + @Override public Object getRowValue(Object[] rowArray) { return rowArray[_columnIndex]; } + @Override public Object getRowValue(Map<String,?> rowMap) { return rowMap.get(_name); } @@ -887,45 +949,25 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { /** * Decodes a date value. */ - private Date readDateValue(ByteBuffer buffer) - { - // seems access stores dates in the local timezone. guess you just hope - // you read it in the same timezone in which it was written! + private Object readDateValue(ByteBuffer buffer) { long dateBits = buffer.getLong(); - long time = fromDateDouble(Double.longBitsToDouble(dateBits)); - return new DateExt(time, dateBits); + return getDateTimeFactory().fromDateBits(this, dateBits); } /** * Returns a java long time value converted from an access date double. * @usage _advanced_method_ */ - public long fromDateDouble(double value) - { - return fromDateDouble(value, getCalendar()); + public long fromDateDouble(double value) { + return fromDateDouble(value, getTimeZone()); } - /** - * Returns a java long time value converted from an access date double. - * @usage _advanced_method_ - */ - public static long fromDateDouble(double value, DatabaseImpl db) - { - return fromDateDouble(value, db.getCalendar()); - } - - /** - * Returns a java long time value converted from an access date double. - * @usage _advanced_method_ - */ - public static long fromDateDouble(double value, Calendar c) - { + private static long fromDateDouble(double value, TimeZone tz) { long localTime = fromLocalDateDouble(value); - return localTime - getFromLocalTimeZoneOffset(localTime, c); + return localTime - getFromLocalTimeZoneOffset(localTime, tz); } - static long fromLocalDateDouble(double value) - { + static long fromLocalDateDouble(double value) { long datePart = ((long)value) * MILLISECONDS_PER_DAY; // the fractional part of the double represents the time. it is always @@ -933,29 +975,49 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { // _not_ the time distance from zero (as one would expect with "normal" // numbers). therefore, we need to do a little number logic to convert // the absolute time fraction into a normal distance from zero number. - long timePart = Math.round((Math.abs(value) % 1.0) * - (double)MILLISECONDS_PER_DAY); + long timePart = Math.round((Math.abs(value) % 1.0d) * + MILLISECONDS_PER_DAY); long time = datePart + timePart; - time -= MILLIS_BETWEEN_EPOCH_AND_1900; - return time; + return time - MILLIS_BETWEEN_EPOCH_AND_1900; + } + + public static LocalDateTime ldtFromLocalDateDouble(double value) { + Duration dateTimeOffset = durationFromLocalDateDouble(value); + return BASE_LDT.plus(dateTimeOffset); + } + + private static Duration durationFromLocalDateDouble(double value) { + long dateSeconds = ((long)value) * SECONDS_PER_DAY; + + // the fractional part of the double represents the time. it is always + // a positive fraction of the day (even if the double is negative), + // _not_ the time distance from zero (as one would expect with "normal" + // numbers). therefore, we need to do a little number logic to convert + // the absolute time fraction into a normal distance from zero number. + + double secondsDouble = (Math.abs(value) % 1.0d) * SECONDS_PER_DAY; + long timeSeconds = (long)secondsDouble; + long timeMillis = (long)(roundToMillis(secondsDouble % 1.0d) * + MILLIS_PER_SECOND); + + return Duration.ofSeconds(dateSeconds + timeSeconds, + timeMillis * NANOS_PER_MILLI); } /** * Writes a date value. */ private void writeDateValue(ByteBuffer buffer, Object value) + throws InvalidValueException { if(value == null) { buffer.putDouble(0d); } else if(value instanceof DateExt) { - // this is a Date value previously read from readDateValue(). use the // original bits to store the value so we don't lose any precision buffer.putLong(((DateExt)value).getDateBits()); - } else { - buffer.putDouble(toDateDouble(value)); } } @@ -966,36 +1028,86 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { * @usage _advanced_method_ */ public double toDateDouble(Object value) + throws InvalidValueException { - return toDateDouble(value, getCalendar()); + try { + return toDateDouble(value, this); + } catch(IllegalArgumentException iae) { + throw new InvalidValueException(withErrorContext(iae.getMessage()), iae); + } } /** - * Returns an access date double converted from a java Date/Calendar/Number - * time value. + * Returns an access date double converted from a java + * Date/Calendar/Number/Temporal time value. * @usage _advanced_method_ */ - public static double toDateDouble(Object value, DatabaseImpl db) + private static double toDateDouble(Object value, ZoneContext zc) { - return toDateDouble(value, db.getCalendar()); - } + if(value instanceof TemporalAccessor) { + return toDateDouble(toLocalDateTime((TemporalAccessor)value, zc)); + } - /** - * Returns an access date double converted from a java Date/Calendar/Number - * time value. - * @usage _advanced_method_ - */ - public static double toDateDouble(Object value, Calendar c) - { // seems access stores dates in the local timezone. guess you just // hope you read it in the same timezone in which it was written! long time = toDateLong(value); - time += getToLocalTimeZoneOffset(time, c); + time += getToLocalTimeZoneOffset(time, zc.getTimeZone()); return toLocalDateDouble(time); } - static double toLocalDateDouble(long time) - { + private static LocalDateTime toLocalDateTime( + TemporalAccessor value, ZoneContext zc) { + + // handle some common Temporal types + if(value instanceof LocalDateTime) { + return (LocalDateTime)value; + } + if(value instanceof ZonedDateTime) { + // if the temporal value has a timezone, convert it to this db's timezone + return ((ZonedDateTime)value).withZoneSameInstant( + zc.getZoneId()).toLocalDateTime(); + } + if(value instanceof Instant) { + return LocalDateTime.ofInstant((Instant)value, zc.getZoneId()); + } + if(value instanceof LocalDate) { + return ((LocalDate)value).atTime(BASE_LT); + } + if(value instanceof LocalTime) { + return ((LocalTime)value).atDate(BASE_LD); + } + + // generic handling for many other Temporal types + try { + + LocalDate ld = value.query(TemporalQueries.localDate()); + if(ld == null) { + ld = BASE_LD; + } + LocalTime lt = value.query(TemporalQueries.localTime()); + if(lt == null) { + lt = BASE_LT; + } + ZoneId zone = value.query(TemporalQueries.zone()); + if(zone != null) { + // the Temporal has a zone, see if it is the right zone. if not, + // adjust it + ZoneId zoneId = zc.getZoneId(); + if(!zoneId.equals(zone)) { + return ZonedDateTime.of(ld, lt, zone).withZoneSameInstant(zoneId) + .toLocalDateTime(); + } + } + + return LocalDateTime.of(ld, lt); + + } catch(DateTimeException | ArithmeticException e) { + throw new IllegalArgumentException( + "Unsupported temporal type " + value.getClass(), e); + } + } + + static double toLocalDateDouble(long time) { time += MILLIS_BETWEEN_EPOCH_AND_1900; if(time < 0L) { @@ -1009,11 +1121,63 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return time / (double)MILLISECONDS_PER_DAY; } + public static double toDateDouble(LocalDateTime ldt) { + Duration dateTimeOffset = Duration.between(BASE_LDT, ldt); + return toLocalDateDouble(dateTimeOffset); + } + + private static double toLocalDateDouble(Duration time) { + long dateTimeSeconds = time.getSeconds(); + long timeSeconds = dateTimeSeconds % SECONDS_PER_DAY; + if(timeSeconds < 0) { + timeSeconds += SECONDS_PER_DAY; + } + long dateSeconds = dateTimeSeconds - timeSeconds; + long timeNanos = time.getNano(); + + // we have a difficult choice to make here between keeping a value which + // most accurately represents the bits saved and rounding to a value that + // would match what the user would expect too see. since we do a double + // to long conversion, we end up in a situation where the value might be + // 19.9999 seconds. access will display this as 20 seconds (access seems + // to only record times to second precision). if we return 19.9999, then + // when the value is written back out it will be exactly the same double + // (good), but will display as 19 seconds (bad because it looks wrong to + // the user). on the flip side, if we round, the value will display + // "correctly" to the user, but if the value is written back out, it will + // be a slightly different double value. this may not be a problem for + // most situations, but may result in incorrect index based lookups. in + // the old date time handling we use DateExt to store the original bits. + // in jdk8, we cannot extend LocalDateTime. for now, we will try + // returning the value rounded to milliseconds (technically still more + // precision than access uses but more likely to round trip to the same + // value). + double timeDouble = ((roundToMillis((double)timeNanos / NANOS_PER_SECOND) + + timeSeconds) / SECONDS_PER_DAY); + + double dateDouble = ((double)dateSeconds / SECONDS_PER_DAY); + + if(dateSeconds < 0) { + timeDouble = -timeDouble; + } + + return dateDouble + timeDouble; + } + + /** + * Rounds the given decimal to milliseconds (3 decimal places) using the + * standard access rounding mode. + */ + private static double roundToMillis(double dbl) { + return ((dbl == 0d) ? dbl : + new BigDecimal(dbl).setScale(3, NumberFormatter.ROUND_MODE) + .doubleValue()); + } + /** * @return an appropriate Date long value for the given object */ - private static long toDateLong(Object value) - { + private static long toDateLong(Object value) { return ((value instanceof Date) ? ((Date)value).getTime() : ((value instanceof Calendar) ? @@ -1025,24 +1189,19 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { * Gets the timezone offset from UTC to local time for the given time * (including DST). */ - private static long getToLocalTimeZoneOffset(long time, Calendar c) - { - c.setTimeInMillis(time); - return ((long)c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET)); + private static long getToLocalTimeZoneOffset(long time, TimeZone tz) { + return tz.getOffset(time); } /** * Gets the timezone offset from local time to UTC for the given time * (including DST). */ - private static long getFromLocalTimeZoneOffset(long time, Calendar c) - { + private static long getFromLocalTimeZoneOffset(long time, TimeZone tz) { // getting from local time back to UTC is a little wonky (and not - // guaranteed to get you back to where you started) - c.setTimeInMillis(time); - // apply the zone offset first to get us closer to the original time - c.setTimeInMillis(time - c.get(Calendar.ZONE_OFFSET)); - return ((long)c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET)); + // guaranteed to get you back to where you started). apply the zone + // offset first to get us closer to the original time + return tz.getOffset(time - tz.getRawOffset()); } /** @@ -1552,6 +1711,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { * Orders Columns by column number. * @usage _general_method_ */ + @Override public int compareTo(ColumnImpl other) { if (_columnNumber > other.getColumnNumber()) { return 1; @@ -1607,6 +1767,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return ((Boolean)value) ? BigDecimal.valueOf(-1) : BigDecimal.ZERO; } else if(value instanceof Date) { return new BigDecimal(toDateDouble(value, db)); + } else if(value instanceof LocalDateTime) { + return new BigDecimal(toDateDouble((LocalDateTime)value)); } return new BigDecimal(value.toString()); } @@ -1637,6 +1799,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return ((Boolean)value) ? -1 : 0; } else if(value instanceof Date) { return toDateDouble(value, db); + } else if(value instanceof LocalDateTime) { + return toDateDouble((LocalDateTime)value); } return Double.valueOf(value.toString()); } @@ -1972,6 +2136,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { DatabaseImpl db) throws IOException { + return toInternalValue(dataType, value, db, null); + } + + static Object toInternalValue(DataType dataType, Object value, + DatabaseImpl db, + ColumnImpl.DateTimeFactory factory) + throws IOException + { if(value == null) { return null; } @@ -1996,8 +2168,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return ((value instanceof Double) ? value : toNumber(value, db).doubleValue()); case SHORT_DATE_TIME: - return ((value instanceof Date) ? value : - new Date(toDateLong(value))); + if(factory == null) { + factory = db.getDateTimeFactory(); + } + return factory.toInternalValue(db, value); case TEXT: case MEMO: case GUID: @@ -2017,6 +2191,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { } } + static DateTimeFactory getDateTimeFactory(DateTimeType type) { + return ((type == DateTimeType.LOCAL_DATE_TIME) ? + LDT_DATE_TIME_FACTORY : DEF_DATE_TIME_FACTORY); + } + String withErrorContext(String msg) { return withErrorContext(msg, getDatabase(), getTable().getName(), getName()); } @@ -2034,8 +2213,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { /** * Date subclass which stashes the original date bits, in case we attempt to - * re-write the value (will not lose precision). + * re-write the value (will not lose precision). Also, this implementation + * is immutable. */ + @SuppressWarnings("deprecation") private static final class DateExt extends Date { private static final long serialVersionUID = 0L; @@ -2052,6 +2233,41 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { return _dateBits; } + @Override + public void setDate(int time) { + throw new UnsupportedOperationException(); + } + + @Override + public void setHours(int time) { + throw new UnsupportedOperationException(); + } + + @Override + public void setMinutes(int time) { + throw new UnsupportedOperationException(); + } + + @Override + public void setMonth(int time) { + throw new UnsupportedOperationException(); + } + + @Override + public void setSeconds(int time) { + throw new UnsupportedOperationException(); + } + + @Override + public void setYear(int time) { + throw new UnsupportedOperationException(); + } + + @Override + public void setTime(long time) { + throw new UnsupportedOperationException(); + } + private Object writeReplace() throws ObjectStreamException { // if we are going to serialize this Date, convert it back to a normal // Date (in case it is restored outside of the context of jackcess) @@ -2456,4 +2672,65 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { sb.append("allowZeroLength=false"); } } + + /** + * Factory which handles date/time values appropriately for a DateTimeType. + */ + static abstract class DateTimeFactory + { + public abstract DateTimeType getType(); + + public abstract Object fromDateBits(ColumnImpl col, long dateBits); + + public abstract Object toInternalValue(DatabaseImpl db, Object value); + } + + /** + * Factory impl for legacy Date handling. + */ + static final class DefaultDateTimeFactory extends DateTimeFactory + { + @Override + public DateTimeType getType() { + return DateTimeType.DATE; + } + + @Override + public Object fromDateBits(ColumnImpl col, long dateBits) { + long time = col.fromDateDouble( + Double.longBitsToDouble(dateBits)); + return new DateExt(time, dateBits); + } + + @Override + public Object toInternalValue(DatabaseImpl db, Object value) { + return ((value instanceof Date) ? value : + new Date(toDateLong(value))); + } + } + + /** + * Factory impl for LocalDateTime handling. + */ + static final class LDTDateTimeFactory extends DateTimeFactory + { + @Override + public DateTimeType getType() { + return DateTimeType.LOCAL_DATE_TIME; + } + + @Override + public Object fromDateBits(ColumnImpl col, long dateBits) { + return ldtFromLocalDateDouble(Double.longBitsToDouble(dateBits)); + } + + @Override + public Object toInternalValue(DatabaseImpl db, Object value) { + if(value instanceof TemporalAccessor) { + return toLocalDateTime((TemporalAccessor)value, db); + } + Instant inst = Instant.ofEpochMilli(toDateLong(value)); + return LocalDateTime.ofInstant(inst, db.getZoneId()); + } + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/CompoundOleUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/CompoundOleUtil.java index 8440f81..9858426 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/CompoundOleUtil.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/CompoundOleUtil.java @@ -32,16 +32,16 @@ import com.healthmarketscience.jackcess.RuntimeIOException; import static com.healthmarketscience.jackcess.impl.OleUtil.*; import com.healthmarketscience.jackcess.util.MemFileChannel; import static com.healthmarketscience.jackcess.util.OleBlob.*; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.poi.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.DocumentInputStream; -import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; /** * Utility code for working with OLE data which is in the compound storage * format. This functionality relies on the optional POI library. - * <p/> + * <br> * Note that all POI usage is restricted to this file so that the basic ole * support in OleUtil can be utilized without requiring POI. * @@ -57,7 +57,7 @@ public class CompoundOleUtil implements CompoundPackageFactory static { // force a poi class to be loaded to ensure that when this class is // loaded, we know that the poi classes are available - NPOIFSFileSystem.class.getName(); + POIFSFileSystem.class.getName(); } public CompoundOleUtil() @@ -139,7 +139,7 @@ public class CompoundOleUtil implements CompoundPackageFactory extends EmbeddedPackageContentImpl implements CompoundContent { - private NPOIFSFileSystem _fs; + private POIFSFileSystem _fs; private CompoundContentImpl( OleBlobImpl blob, String prettyName, String className, @@ -152,9 +152,9 @@ public class CompoundOleUtil implements CompoundPackageFactory return ContentType.COMPOUND_STORAGE; } - private NPOIFSFileSystem getFileSystem() throws IOException { + private POIFSFileSystem getFileSystem() throws IOException { if(_fs == null) { - _fs = new NPOIFSFileSystem(MemFileChannel.newChannel(getStream(), "r")); + _fs = new POIFSFileSystem(MemFileChannel.newChannel(getStream(), "r")); } return _fs; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java index 85a53dd..bc24e46 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java @@ -299,7 +299,7 @@ public abstract class CursorImpl implements Cursor * Moves to another row in the table based on the given direction and * returns it. * @param columnNames Only column names in this collection will be returned - * @return another row in this table (Column name -> Column value), where + * @return another row in this table (Column name -> Column value), where * "next" may be backwards if moveForward is {@code false}, or * {@code null} if there is not another row in the given direction. */ diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/CustomToStringStyle.java b/src/main/java/com/healthmarketscience/jackcess/impl/CustomToStringStyle.java index 707e163..89ef061 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/CustomToStringStyle.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/CustomToStringStyle.java @@ -21,9 +21,8 @@ import java.util.Collection; import java.util.Iterator; import java.util.Map; -import org.apache.commons.lang.SystemUtils; -import org.apache.commons.lang.builder.StandardToStringStyle; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.StandardToStringStyle; +import org.apache.commons.lang3.builder.ToStringBuilder; /** * Custom ToStringStyle for use with ToStringBuilder. @@ -34,7 +33,7 @@ public class CustomToStringStyle extends StandardToStringStyle { private static final long serialVersionUID = 0L; - private static final String ML_FIELD_SEP = SystemUtils.LINE_SEPARATOR + " "; + private static final String ML_FIELD_SEP = System.lineSeparator() + " "; private static final String IMPL_SUFFIX = "Impl"; private static final int MAX_BYTE_DETAIL_LEN = 20; private static final Object IGNORE_ME = new Object(); @@ -47,7 +46,7 @@ public class CustomToStringStyle extends StandardToStringStyle setFieldSeparatorAtStart(true); setFieldNameValueSeparator(": "); setArraySeparator("," + ML_FIELD_SEP); - setContentEnd(SystemUtils.LINE_SEPARATOR + "]"); + setContentEnd(System.lineSeparator() + "]"); setUseShortClassName(true); } }; @@ -91,7 +90,7 @@ public class CustomToStringStyle extends StandardToStringStyle } @Override - protected String getShortClassName(Class clss) { + protected String getShortClassName(Class<?> clss) { String shortName = super.getShortClassName(clss); if(shortName.endsWith(IMPL_SUFFIX)) { shortName = shortName.substring(0, @@ -116,7 +115,7 @@ public class CustomToStringStyle extends StandardToStringStyle @Override protected void appendDetail(StringBuffer buffer, String fieldName, - Collection value) { + Collection<?> value) { buffer.append("["); // gather contents of list in a new StringBuffer @@ -145,13 +144,12 @@ public class CustomToStringStyle extends StandardToStringStyle @Override protected void appendDetail(StringBuffer buffer, String fieldName, - Map value) { + Map<?,?> value) { buffer.append("{"); // gather contents of map in a new StringBuffer StringBuffer sb = new StringBuffer(); - @SuppressWarnings("unchecked") - Iterator<Map.Entry<?,?>> iter = value.entrySet().iterator(); + Iterator<? extends Map.Entry<?,?>> iter = value.entrySet().iterator(); if(iter.hasNext()) { if(isFieldSeparatorAtStart()) { appendFieldSeparator(sb); @@ -203,7 +201,7 @@ public class CustomToStringStyle extends StandardToStringStyle private static String indent(Object obj) { return ((obj != null) ? obj.toString().replaceAll( - SystemUtils.LINE_SEPARATOR, ML_FIELD_SEP) : null); + System.lineSeparator(), ML_FIELD_SEP) : null); } public static Object ignoreNull(Object obj) { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java index 7f50f68..2fbec97 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java @@ -17,8 +17,8 @@ limitations under the License. package com.healthmarketscience.jackcess.impl; import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Map; import javax.script.Bindings; import javax.script.SimpleBindings; @@ -42,7 +42,7 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig private final DatabaseImpl _db; private FunctionLookup _funcs = DefaultFunctions.LOOKUP; - private Map<String,SimpleDateFormat> _sdfs; + private Map<String,DateTimeFormatter> _sdfs; private Map<String,DecimalFormat> _dfs; private TemporalConfig _temporal = TemporalConfig.US_TEMPORAL_CONFIG; private NumericConfig _numeric = NumericConfig.US_NUMERIC_CONFIG; @@ -68,8 +68,8 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig } } - public Calendar getCalendar() { - return _db.getCalendar(); + public ZoneId getZoneId() { + return _db.getZoneId(); } public NumericConfig getNumericConfig() { @@ -99,14 +99,13 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig _bindings = bindings; } - public SimpleDateFormat createDateFormat(String formatStr) { + public DateTimeFormatter createDateFormatter(String formatStr) { if(_sdfs == null) { - _sdfs = new SimpleCache<String,SimpleDateFormat>(MAX_CACHE_SIZE); + _sdfs = new SimpleCache<String,DateTimeFormatter>(MAX_CACHE_SIZE); } - SimpleDateFormat sdf = _sdfs.get(formatStr); + DateTimeFormatter sdf = _sdfs.get(formatStr); if(sdf == null) { - sdf = _db.createDateFormat(formatStr); - sdf.setDateFormatSymbols(_temporal.getDateFormatSymbols()); + sdf = DateTimeFormatter.ofPattern(formatStr, _temporal.getLocale()); _sdfs.put(formatStr, sdf); } return sdf; @@ -128,8 +127,4 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig public float getRandom(Integer seed) { return _rndCtx.getRandom(seed); } - - void resetDateTimeConfig() { - _sdfs = null; - } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java index bb74bba..dcd4e18 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java @@ -20,7 +20,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.RandomAccessFile; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; @@ -28,7 +27,12 @@ import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.text.SimpleDateFormat; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -53,6 +57,7 @@ import com.healthmarketscience.jackcess.CursorBuilder; import com.healthmarketscience.jackcess.DataType; import com.healthmarketscience.jackcess.Database; import com.healthmarketscience.jackcess.DatabaseBuilder; +import com.healthmarketscience.jackcess.DateTimeType; import com.healthmarketscience.jackcess.Index; import com.healthmarketscience.jackcess.IndexBuilder; import com.healthmarketscience.jackcess.IndexCursor; @@ -73,8 +78,8 @@ import com.healthmarketscience.jackcess.util.LinkResolver; import com.healthmarketscience.jackcess.util.ReadOnlyFileChannel; import com.healthmarketscience.jackcess.util.SimpleColumnValidatorFactory; import com.healthmarketscience.jackcess.util.TableIterableBuilder; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -84,7 +89,7 @@ import org.apache.commons.logging.LogFactory; * @author Tim McCune * @usage _intermediate_class_ */ -public class DatabaseImpl implements Database +public class DatabaseImpl implements Database, ZoneContext { private static final Log LOG = LogFactory.getLog(DatabaseImpl.class); @@ -198,9 +203,11 @@ public class DatabaseImpl implements Database SYSTEM_OBJECT_FLAG | ALT_SYSTEM_OBJECT_FLAG; /** read-only channel access mode */ - public static final String RO_CHANNEL_MODE = "r"; + public static final OpenOption[] RO_CHANNEL_OPTS = + {StandardOpenOption.READ}; /** read/write channel access mode */ - public static final String RW_CHANNEL_MODE = "rw"; + public static final OpenOption[] RW_CHANNEL_OPTS = + {StandardOpenOption.READ, StandardOpenOption.WRITE}; /** Name of the system object that is the parent of all tables */ private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables"; @@ -252,7 +259,7 @@ public class DatabaseImpl implements Database Pattern.compile("[\\p{Cntrl}.!`\\]\\[]"); /** the File of the database */ - private final File _file; + private final Path _file; /** the simple name of the database */ private final String _name; /** whether or not this db is read-only */ @@ -300,6 +307,8 @@ public class DatabaseImpl implements Database private Charset _charset; /** timezone to use when handling dates */ private TimeZone _timeZone; + /** zoneId to use when handling dates */ + private ZoneId _zoneId; /** language sort order to be used for textual columns */ private ColumnImpl.SortOrder _defaultSortOrder; /** default code page to be used for textual columns (in some dbs) */ @@ -335,10 +344,11 @@ public class DatabaseImpl implements Database /** shared state used when enforcing foreign keys */ private final FKEnforcer.SharedState _fkEnforcerSharedState = FKEnforcer.initSharedState(); - /** Calendar for use interpreting dates/times in Columns */ - private Calendar _calendar; /** shared context for evaluating expressions */ private DBEvalContext _evalCtx; + /** factory for the appropriate date/time type */ + private ColumnImpl.DateTimeFactory _dtf = + ColumnImpl.getDateTimeFactory(DateTimeType.DATE); /** * Open an existing Database. If the existing file is not writeable or the @@ -364,20 +374,20 @@ public class DatabaseImpl implements Database * @usage _advanced_method_ */ public static DatabaseImpl open( - File mdbFile, boolean readOnly, FileChannel channel, + Path mdbFile, boolean readOnly, FileChannel channel, boolean autoSync, Charset charset, TimeZone timeZone, CodecProvider provider) throws IOException { boolean closeChannel = false; if(channel == null) { - if(!mdbFile.exists() || !mdbFile.canRead()) { + if(!Files.isReadable(mdbFile)) { throw new FileNotFoundException("given file does not exist: " + mdbFile); } // force read-only for non-writable files - readOnly |= !mdbFile.canWrite(); + readOnly |= !Files.isWritable(mdbFile); // open file channel channel = openChannel(mdbFile, readOnly); @@ -434,7 +444,7 @@ public class DatabaseImpl implements Database * @param timeZone TimeZone to use, if {@code null}, uses default * @usage _advanced_method_ */ - public static DatabaseImpl create(FileFormat fileFormat, File mdbFile, + public static DatabaseImpl create(FileFormat fileFormat, Path mdbFile, FileChannel channel, boolean autoSync, Charset charset, TimeZone timeZone) throws IOException @@ -486,11 +496,11 @@ public class DatabaseImpl implements Database * that name cannot be created, or if some other error occurs * while opening or creating the file */ - static FileChannel openChannel(final File mdbFile, final boolean readOnly) - throws FileNotFoundException + static FileChannel openChannel(Path mdbFile, boolean readOnly) + throws IOException { - final String mode = (readOnly ? RO_CHANNEL_MODE : RW_CHANNEL_MODE); - return new RandomAccessFile(mdbFile, mode).getChannel(); + OpenOption[] opts = (readOnly ? RO_CHANNEL_OPTS : RW_CHANNEL_OPTS); + return FileChannel.open(mdbFile, opts); } /** @@ -512,7 +522,7 @@ public class DatabaseImpl implements Database * @param charset Charset to use, if {@code null}, uses default * @param timeZone TimeZone to use, if {@code null}, uses default */ - protected DatabaseImpl(File file, FileChannel channel, boolean closeChannel, + protected DatabaseImpl(Path file, FileChannel channel, boolean closeChannel, boolean autoSync, FileFormat fileFormat, Charset charset, TimeZone timeZone, CodecProvider provider, boolean readOnly) @@ -529,7 +539,7 @@ public class DatabaseImpl implements Database _evaluateExpressions = getDefaultEvaluateExpressions(); _fileFormat = fileFormat; _pageChannel = new PageChannel(channel, closeChannel, _format, autoSync); - _timeZone = ((timeZone == null) ? getDefaultTimeZone() : timeZone); + setZoneInfo(timeZone, null); if(provider == null) { provider = DefaultCodecProvider.INSTANCE; } @@ -542,6 +552,10 @@ public class DatabaseImpl implements Database } public File getFile() { + return ((_file != null) ? _file.toFile() : null); + } + + public Path getPath() { return _file; } @@ -645,20 +659,51 @@ public class DatabaseImpl implements Database (_linkedDbs.get(linkedDbName) == table.getDatabase())); } + @Override public TimeZone getTimeZone() { return _timeZone; } + @Override public void setTimeZone(TimeZone newTimeZone) { - if(newTimeZone == null) { + setZoneInfo(newTimeZone, null); + } + + @Override + public ZoneId getZoneId() { + return _zoneId; + } + + public void setZoneId(ZoneId newZoneId) { + setZoneInfo(null, newZoneId); + } + + private void setZoneInfo(TimeZone newTimeZone, ZoneId newZoneId) { + if(newTimeZone != null) { + newZoneId = newTimeZone.toZoneId(); + } else if(newZoneId != null) { + newTimeZone = TimeZone.getTimeZone(newZoneId); + } else { newTimeZone = getDefaultTimeZone(); + newZoneId = newTimeZone.toZoneId(); } + _timeZone = newTimeZone; - // clear cached calendar(s) when timezone is changed - _calendar = null; - if(_evalCtx != null) { - _evalCtx.resetDateTimeConfig(); - } + _zoneId = newZoneId; + } + + @Override + public DateTimeType getDateTimeType() { + return _dtf.getType(); + } + + @Override + public void setDateTimeType(DateTimeType dateTimeType) { + _dtf = ColumnImpl.getDateTimeFactory(dateTimeType); + } + + protected ColumnImpl.DateTimeFactory getDateTimeFactory() { + return _dtf; } public Charset getCharset() @@ -735,17 +780,6 @@ public class DatabaseImpl implements Database return _fkEnforcerSharedState; } - /** - * @usage _advanced_method_ - */ - Calendar getCalendar() { - if(_calendar == null) { - _calendar = DatabaseBuilder.toCompatibleCalendar( - Calendar.getInstance(_timeZone)); - } - return _calendar; - } - public EvalConfig getEvalConfig() { return getEvalContext(); } @@ -2106,11 +2140,11 @@ public class DatabaseImpl implements Database FILE_FORMAT_DETAILS.put(fileFormat, new FileFormatDetails(emptyFile, format)); } - private static String getName(File file) { + private static String getName(Path file) { if(file == null) { return "<UNKNOWN.DB>"; } - return file.getName(); + return file.getFileName().toString(); } private String withErrorContext(String msg) { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java index e2b2dd5..f210a0a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java @@ -33,7 +33,7 @@ import com.healthmarketscience.jackcess.IndexBuilder; import com.healthmarketscience.jackcess.RuntimeIOException; import static com.healthmarketscience.jackcess.impl.ByteUtil.ByteStream; import static com.healthmarketscience.jackcess.impl.IndexCodes.*; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -41,17 +41,17 @@ import org.apache.commons.logging.LogFactory; * Access table index data. This is the actual data which backs a logical * Index, where one or more logical indexes can be backed by the same index * data. - * + * * @author Tim McCune */ public class IndexData { - + protected static final Log LOG = LogFactory.getLog(Index.class); /** special entry which is less than any other entry */ public static final Entry FIRST_ENTRY = createSpecialEntry(RowIdImpl.FIRST_ROW_ID); - + /** special entry which is greater than any other entry */ public static final Entry LAST_ENTRY = createSpecialEntry(RowIdImpl.LAST_ROW_ID); @@ -65,12 +65,12 @@ public class IndexData { public static final Object MIN_VALUE = new Object(); private static final DataPage NEW_ROOT_DATA_PAGE = new RootDataPage(); - - protected static final int INVALID_INDEX_PAGE_NUMBER = 0; - + + protected static final int INVALID_INDEX_PAGE_NUMBER = 0; + /** Max number of columns in an index */ public static final int MAX_COLUMNS = 10; - + protected static final byte[] EMPTY_PREFIX = new byte[0]; static final short COLUMN_UNUSED = -1; @@ -85,7 +85,7 @@ public class IndexData { private static final int MAGIC_INDEX_NUMBER = 1923; private static final ByteOrder ENTRY_BYTE_ORDER = ByteOrder.BIG_ENDIAN; - + /** type attributes for Entries which simplify comparisons */ public enum EntryType { /** comparable type indicating this Entry should always compare less than @@ -104,7 +104,7 @@ public class IndexData { than valid RowIds */ ALWAYS_LAST; } - + public static final Comparator<byte[]> BYTE_CODE_COMPARATOR = new Comparator<byte[]>() { public int compare(byte[] left, byte[] right) { @@ -131,8 +131,8 @@ public class IndexData { ((left.length > right.length) ? 1 : 0)); } }; - - + + /** name, generated on demand */ private String _name; /** owning table */ @@ -175,7 +175,7 @@ public class IndexData { private String _unsupportedReason; /** Cache which manages the index pages */ private final IndexPageCache _pageCache; - + protected IndexData(TableImpl table, int number, int uniqueEntryCount, int uniqueEntryCountOffset) { @@ -216,14 +216,14 @@ public class IndexData { } else { _name = String.valueOf(_number); } - } + } return _name; } public TableImpl getTable() { return _table; } - + public JetFormat getFormat() { return getTable().getFormat(); } @@ -280,7 +280,7 @@ public class IndexData { public int getIndexDataNumber() { return _number; } - + public int getUniqueEntryCount() { return _uniqueEntryCount; } @@ -315,7 +315,7 @@ public class IndexData { public boolean isUnique() { return(isBackingPrimaryKey() || ((_indexFlags & UNIQUE_INDEX_FLAG) != 0)); } - + /** * Whether or not values are required in the columns. */ @@ -345,7 +345,7 @@ public class IndexData { return _rootPageNumber; } - private void setUnsupportedReason(String reason, ColumnImpl col) { + private void setUnsupportedReason(String reason, ColumnImpl col) { _unsupportedReason = withErrorContext(reason); if(!col.getTable().isSystem()) { LOG.warn(_unsupportedReason + ", making read-only"); @@ -371,7 +371,7 @@ public class IndexData { public int getOwnedPageCount() { return _ownedPages.getPageCount(); } - + void addOwnedPage(int pageNumber) throws IOException { _ownedPages.addPageNumber(pageNumber); } @@ -379,7 +379,7 @@ public class IndexData { void collectUsageMapPages(Collection<Integer> pages) { pages.add(_ownedPages.getTablePageNumber()); } - + /** * Used by unit tests to validate the internal status of the index. * @usage _advanced_method_ @@ -407,7 +407,7 @@ public class IndexData { } return count; } - + /** * Forces initialization of this index (actual parsing of index pages). * normally, the index will not be initialized until the entries are @@ -429,7 +429,7 @@ public class IndexData { { // make sure we've parsed the entries initialize(); - + if(_unsupportedReason != null) { throw new UnsupportedOperationException( "Cannot write indexes of this type due to " + _unsupportedReason); @@ -470,7 +470,7 @@ public class IndexData { } _ownedPages = UsageMap.read(getTable().getDatabase(), tableBuffer); - + _rootPageNumber = tableBuffer.getInt(); ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX_FLAGS); //Forward past Unknown @@ -525,7 +525,7 @@ public class IndexData { * @param buffer Buffer to write to */ protected static void writeDefinition( - TableMutator creator, ByteBuffer buffer, + TableMutator creator, ByteBuffer buffer, TableMutator.IndexDataState idxDataState, ByteBuffer rootPageBuffer) throws IOException { @@ -559,7 +559,7 @@ public class IndexData { creator.getDatabase(), creator.getTableName(), idx.getName())); } } - + buffer.putShort(columnNumber); // table column number buffer.put(flags); // column flags (e.g. ordering) } @@ -568,7 +568,7 @@ public class IndexData { ByteUtil.put3ByteInt(buffer, idxDataState.getUmapPageNumber()); // umap page // write empty root index page - creator.getPageChannel().writePage(rootPageBuffer, + creator.getPageChannel().writePage(rootPageBuffer, idxDataState.getRootPageNumber()); buffer.putInt(idxDataState.getRootPageNumber()); @@ -577,11 +577,11 @@ public class IndexData { ByteUtil.forward(buffer, 5); // unknown } - private static ByteBuffer createRootPageBuffer(TableMutator creator) + private static ByteBuffer createRootPageBuffer(TableMutator creator) throws IOException { ByteBuffer rootPageBuffer = creator.getPageChannel().createPageBuffer(); - writeDataPage(rootPageBuffer, NEW_ROOT_DATA_PAGE, + writeDataPage(rootPageBuffer, NEW_ROOT_DATA_PAGE, creator.getTdefPageNumber(), creator.getFormat()); return rootPageBuffer; } @@ -591,7 +591,7 @@ public class IndexData { * this method returns. * <p> * Forces index initialization. - * + * * @param row Row to add * @param rowId rowId of the row to be added * @@ -603,7 +603,7 @@ public class IndexData { { return prepareAddRow(row, rowId, new AddRowPendingChange(nextChange)); } - + private PendingChange prepareAddRow(Object[] row, RowIdImpl rowId, AddRowPendingChange change) throws IOException @@ -619,7 +619,7 @@ public class IndexData { "Null value found in row " + Arrays.asList(row) + " for primary key or required index")); } - + // make sure we've parsed the entries initialize(); @@ -637,14 +637,14 @@ public class IndexData { DataPage dataPage = findDataPage(newEntry); int idx = dataPage.findEntry(newEntry); if(idx < 0) { - + // this is a new entry idx = missingIndexToInsertionPoint(idx); Position newPos = new Position(dataPage, idx, newEntry, true); Position nextPos = getNextPosition(newPos); Position prevPos = getPreviousPosition(newPos); - + // determine if the addition of this entry would break the uniqueness // constraint. See isUnique() for some notes about uniqueness as // defined by Access. @@ -691,7 +691,7 @@ public class IndexData { * before this method returns. * <p> * Forces index initialization. - * + * * @param oldRow Row to be removed * @param newRow Row to be added * @param rowId rowId of the row to be updated @@ -699,7 +699,7 @@ public class IndexData { * @return a PendingChange which can complete the update or roll it back */ public PendingChange prepareUpdateRow(Object[] oldRow, RowIdImpl rowId, - Object[] newRow, + Object[] newRow, PendingChange nextChange) throws IOException { @@ -715,12 +715,12 @@ public class IndexData { throw e; } } - + /** * Removes a row from this index * <p> * Forces index initialization. - * + * * @param row Row to remove * @param rowId rowId of the row to be removed */ @@ -729,7 +729,7 @@ public class IndexData { { deleteRowImpl(row, rowId); } - + private Entry deleteRowImpl(Object[] row, RowIdImpl rowId) throws IOException { @@ -738,7 +738,7 @@ public class IndexData { // nothing to do return null; } - + // make sure we've parsed the entries initialize(); @@ -748,7 +748,7 @@ public class IndexData { ++_modCount; } else { LOG.warn(withErrorContext( - "Failed removing index entry " + oldEntry + " for row: " + + "Failed removing index entry " + oldEntry + " for row: " + Arrays.asList(row))); } return removedEntry; @@ -773,7 +773,7 @@ public class IndexData { dataPage.addEntry(missingIndexToInsertionPoint(idx), removedEntry); } } - + /** * Removes an entry from the relevant index dataPage, maintaining the order. * Will search by RowId if entry is not found (in case a partial entry was @@ -810,10 +810,10 @@ public class IndexData { // found it! removedEntry = dataPage.removeEntry(idx); } - + return removedEntry; } - + public static void commitAll(PendingChange change) throws IOException { while(change != null) { change.commit(); @@ -827,7 +827,7 @@ public class IndexData { change = change.getNext(); } } - + /** * Gets a new cursor for this index. * <p> @@ -838,13 +838,13 @@ public class IndexData { { return cursor(null, true, null, true); } - + /** * Gets a new cursor for this index, narrowed to the range defined by the * given startRow and endRow. * <p> * Forces index initialization. - * + * * @param startRow the first row of data for the cursor, or {@code null} for * the first entry * @param startInclusive whether or not startRow is inclusive or exclusive @@ -1008,13 +1008,13 @@ public class IndexData { int valIdx = 0; Object[] idxRow = new Object[getTable().getColumnCount()]; for(ColumnDescriptor col : _columns) { - idxRow[col.getColumnIndex()] = + idxRow[col.getColumnIndex()] = ((valIdx < values.length) ? values[valIdx] : filler); ++valIdx; } return idxRow; } - + /** * Constructs an array of values appropriate for this index from the given * column value. @@ -1057,7 +1057,7 @@ public class IndexData { idxRow[col.getColumnIndex()] = row.get(col.getName()); } return idxRow; - } + } /** * Constructs an array of values appropriate for this index from the given @@ -1088,7 +1088,7 @@ public class IndexData { Object[] idxRow = new Object[getTable().getColumnCount()]; int valIdx = 0; for(ColumnDescriptor col : _columns) { - idxRow[col.getColumnIndex()] = + idxRow[col.getColumnIndex()] = ((valIdx < numCols) ? row.get(col.getName()) : filler); ++valIdx; } @@ -1116,7 +1116,7 @@ public class IndexData { sb.append("pageCache", _pageCache); return sb.toString(); } - + /** * Write the given index page out to a buffer */ @@ -1126,7 +1126,7 @@ public class IndexData { if(dataPage.getCompressedEntrySize() > _maxPageEntrySize) { throw new IllegalStateException(withErrorContext("data page is too large")); } - + ByteBuffer buffer = _indexBufferH.getPageBuffer(getPageChannel()); writeDataPage(buffer, dataPage, getTable().getTableDefPageNumber(), @@ -1170,7 +1170,7 @@ public class IndexData { // first entry includes the prefix buffer.put(entryPrefix); - + for(Entry entry : dataPage.getEntries()) { entry.write(buffer, entryPrefix); } @@ -1232,7 +1232,7 @@ public class IndexData { "Unexpected order in index entries, " + prevEntry + " >= " + entry)); } - + entries.add(entry); if((entries.size() == 1) && (entryPrefixLength > 0)) { @@ -1251,7 +1251,7 @@ public class IndexData { dataPage.setEntryPrefix(entryPrefix != null ? entryPrefix : EMPTY_PREFIX); dataPage.setEntries(entries); dataPage.setTotalEntrySize(totalEntrySize); - + int prevPageNumber = buffer.getInt(getFormat().OFFSET_PREV_INDEX_PAGE); int nextPageNumber = buffer.getInt(getFormat().OFFSET_NEXT_INDEX_PAGE); int childTailPageNumber = @@ -1265,7 +1265,7 @@ public class IndexData { /** * Returns a new Entry of the correct type for the given data and page type. */ - private static Entry newEntry(ByteBuffer buffer, int entryLength, + private static Entry newEntry(ByteBuffer buffer, int entryLength, boolean isLeaf) throws IOException { @@ -1291,10 +1291,10 @@ public class IndexData { tmpEntryBuffer.put(valuePrefix); tmpEntryBuffer.put(indexPage.array(), indexPage.position(), entryLen); tmpEntryBuffer.flip(); - + return tmpEntryBuffer; } - + /** * Determines if the given index page is a leaf or node page. */ @@ -1309,7 +1309,7 @@ public class IndexData { } throw new IOException(withErrorContext("Unexpected page type " + pageType)); } - + /** * Determines the number of {@code null} values for this index from the * given row. @@ -1319,7 +1319,7 @@ public class IndexData { if(values == null) { return _columns.size(); } - + // annoyingly, the values array could come from different sources, one // of which will make it a different size than the other. we need to // handle both situations. @@ -1330,7 +1330,7 @@ public class IndexData { ++nullCount; } } - + return nullCount; } @@ -1342,12 +1342,12 @@ public class IndexData { if(values == null) { return null; } - + if(_entryBuffer == null) { _entryBuffer = new ByteStream(); } _entryBuffer.reset(); - + for(ColumnDescriptor col : _columns) { Object value = values[col.getColumnIndex()]; @@ -1373,9 +1373,9 @@ public class IndexData { col.writeValue(value, _entryBuffer); } - + return _entryBuffer.toByteArray(); - } + } /** * Finds the data page for the given entry. @@ -1385,7 +1385,7 @@ public class IndexData { { return _pageCache.findCacheDataPage(entry); } - + /** * Gets the data page for the pageNumber. */ @@ -1394,7 +1394,7 @@ public class IndexData { { return _pageCache.getCacheDataPage(pageNumber); } - + /** * Flips the first bit in the byte at the given index. */ @@ -1418,7 +1418,7 @@ public class IndexData { static byte[] flipBytes(byte[] value, int offset, int length) { for(int i = offset; i < (offset + length); ++i) { value[i] = (byte)(~value[i]); - } + } return value; } @@ -1430,7 +1430,7 @@ public class IndexData { { // always write in big endian order return column.write(value, 0, ENTRY_BYTE_ORDER).array(); - } + } /** * Writes a binary value using the general binary entry encoding rules. @@ -1452,7 +1452,7 @@ public class IndexData { // bit twiddling rules: // - isAsc => nothing - // - !isAsc => flipBytes, _but keep intermediate 09 unflipped_! + // - !isAsc => flipBytes, _but keep intermediate 09 unflipped_! // first, write any intermediate segements int segmentLen = dataLen; @@ -1549,7 +1549,7 @@ public class IndexData { default: // we can't modify this index at this point in time - setUnsupportedReason("unsupported data type " + col.getType() + + setUnsupportedReason("unsupported data type " + col.getType() + " for index", col); return new ReadOnlyColumnDescriptor(col, flags); } @@ -1598,7 +1598,7 @@ public class IndexData { return msg + " (Db=" + db.getName() + ";Table=" + tableName + ";Index=" + idxName + ")"; } - + /** * Information about the columns in an index. Also encodes new index * values. @@ -1626,11 +1626,11 @@ public class IndexData { public boolean isAscending() { return((getFlags() & ASCENDING_COLUMN_FLAG) != 0); } - + public int getColumnIndex() { return getColumn().getColumnIndex(); } - + public String getName() { return getColumn().getName(); } @@ -1638,7 +1638,7 @@ public class IndexData { protected boolean isNullValue(Object value) { return (value == null); } - + protected final void writeValue(Object value, ByteStream bout) throws IOException { @@ -1647,7 +1647,7 @@ public class IndexData { bout.write(getNullEntryFlag(isAscending())); return; } - + // write the start flag bout.write(getStartEntryFlag(isAscending())); // write the rest of the value @@ -1655,8 +1655,8 @@ public class IndexData { } protected abstract void writeNonNullValue(Object value, ByteStream bout) - throws IOException; - + throws IOException; + @Override public String toString() { return CustomToStringStyle.builder(this) @@ -1676,26 +1676,26 @@ public class IndexData { { super(column, flags); } - + @Override protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); - + // bit twiddling rules: // - isAsc => flipFirstBit // - !isAsc => flipFirstBit, flipBytes - + flipFirstBitInByte(valueBytes, 0); if(!isAscending()) { flipBytes(valueBytes); } - + bout.write(valueBytes); - } + } } - + /** * ColumnDescriptor for floating point based columns. */ @@ -1707,13 +1707,13 @@ public class IndexData { { super(column, flags); } - + @Override protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); - + // determine if the number is negative by testing if the first bit is // set boolean isNegative = ((valueBytes[0] & 0x80) != 0); @@ -1723,18 +1723,18 @@ public class IndexData { // isAsc && isNeg => flipBytes // !isAsc && !isNeg => flipFirstBit, flipBytes // !isAsc && isNeg => nothing - + if(!isNegative) { flipFirstBitInByte(valueBytes, 0); } if(isNegative == isAscending()) { flipBytes(valueBytes); } - + bout.write(valueBytes); - } + } } - + /** * ColumnDescriptor for fixed point based columns (legacy sort order). */ @@ -1755,15 +1755,15 @@ public class IndexData { } // reverse the sign byte (after any previous byte flipping) - valueBytes[0] = (isNegative ? (byte)0x00 : (byte)0xFF); + valueBytes[0] = (isNegative ? (byte)0x00 : (byte)0xFF); } - + @Override protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); - + // determine if the number is negative by testing if the first bit is // set boolean isNegative = ((valueBytes[0] & 0x80) != 0); @@ -1773,7 +1773,7 @@ public class IndexData { // isAsc && isNeg => flipBytes, setReverseSignByte => 00 FF FF ... // !isAsc && !isNeg => flipBytes, setReverseSignByte => FF FF FF ... // !isAsc && isNeg => setReverseSignByte => 00 00 00 ... - + // v2007 bit twiddling rules (old ordering was a bug, MS kb 837148): // isAsc && !isNeg => setSignByte 0xFF => FF 00 00 ... // isAsc && isNeg => setSignByte 0xFF, flipBytes => 00 FF FF ... @@ -1782,9 +1782,9 @@ public class IndexData { handleNegationAndOrder(isNegative, valueBytes); bout.write(valueBytes); - } + } } - + /** * ColumnDescriptor for new-style fixed point based columns. */ @@ -1796,7 +1796,7 @@ public class IndexData { { super(column, flags); } - + @Override protected void handleNegationAndOrder(boolean isNegative, byte[] valueBytes) @@ -1809,9 +1809,9 @@ public class IndexData { if(isNegative == isAscending()) { flipBytes(valueBytes); } - } + } } - + /** * ColumnDescriptor for byte based columns. */ @@ -1822,24 +1822,24 @@ public class IndexData { { super(column, flags); } - + @Override protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { byte[] valueBytes = encodeNumberColumnValue(value, getColumn()); - + // bit twiddling rules: // - isAsc => nothing // - !isAsc => flipBytes if(!isAscending()) { flipBytes(valueBytes); } - + bout.write(valueBytes); - } + } } - + /** * ColumnDescriptor for boolean columns. */ @@ -1856,7 +1856,7 @@ public class IndexData { // null values are handled as booleans return false; } - + @Override protected void writeNonNullValue(Object value, ByteStream bout) throws IOException @@ -1867,11 +1867,11 @@ public class IndexData { (isAscending() ? ASC_BOOLEAN_FALSE : DESC_BOOLEAN_FALSE)); } } - + /** * ColumnDescriptor for "general legacy" sort order text based columns. */ - private static final class GenLegTextColumnDescriptor + private static final class GenLegTextColumnDescriptor extends ColumnDescriptor { private GenLegTextColumnDescriptor(ColumnImpl column, byte flags) @@ -1879,14 +1879,14 @@ public class IndexData { { super(column, flags); } - + @Override protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { GeneralLegacyIndexCodes.GEN_LEG_INSTANCE.writeNonNullIndexTextValue( value, bout, isAscending()); - } + } } /** @@ -1899,14 +1899,14 @@ public class IndexData { { super(column, flags); } - + @Override protected void writeNonNullValue(Object value, ByteStream bout) throws IOException { GeneralIndexCodes.GEN_INSTANCE.writeNonNullIndexTextValue( value, bout, isAscending()); - } + } } /** @@ -1919,7 +1919,7 @@ public class IndexData { { super(column, flags); } - + @Override protected void writeNonNullValue(Object value, ByteStream bout) throws IOException @@ -1929,7 +1929,7 @@ public class IndexData { bout); } } - + /** * ColumnDescriptor for BINARY columns. @@ -1950,8 +1950,8 @@ public class IndexData { ColumnImpl.toByteArray(value), isAscending(), bout); } } - - + + /** * ColumnDescriptor for columns which we cannot currently write. */ @@ -1971,7 +1971,7 @@ public class IndexData { "Cannot write indexes of this type due to " + _unsupportedReason); } } - + /** * A single leaf entry in an index (points to a single row) */ @@ -1983,7 +1983,7 @@ public class IndexData { private final byte[] _entryBytes; /** comparable type for the entry */ private final EntryType _type; - + /** * Create a new entry * @param entryBytes encoded bytes for this index entry @@ -1995,7 +1995,7 @@ public class IndexData { _entryBytes = entryBytes; _type = type; } - + /** * Create a new entry * @param entryBytes encoded bytes for this index entry @@ -2014,7 +2014,7 @@ public class IndexData { { this(buffer, entryLen, 0); } - + /** * Read an existing entry in from a buffer */ @@ -2031,11 +2031,11 @@ public class IndexData { // read the rowId int page = ByteUtil.get3ByteInt(buffer, ENTRY_BYTE_ORDER); int row = ByteUtil.getUnsignedByte(buffer); - + _rowId = new RowIdImpl(page, row); _type = EntryType.NORMAL; } - + public RowIdImpl getRowId() { return _rowId; } @@ -2047,11 +2047,11 @@ public class IndexData { public Integer getSubPageNumber() { throw new UnsupportedOperationException(); } - + public boolean isLeafEntry() { return true; } - + public boolean isValid() { return(_entryBytes != null); } @@ -2059,7 +2059,7 @@ public class IndexData { protected final byte[] getEntryBytes() { return _entryBytes; } - + /** * Size of this entry in the db. */ @@ -2067,7 +2067,7 @@ public class IndexData { // need 4 trailing bytes for the rowId return _entryBytes.length + 4; } - + /** * Write this entry into a buffer */ @@ -2076,15 +2076,15 @@ public class IndexData { throws IOException { if(prefix.length <= _entryBytes.length) { - + // write entry bytes, not including prefix buffer.put(_entryBytes, prefix.length, (_entryBytes.length - prefix.length)); ByteUtil.put3ByteInt(buffer, getRowId().getPageNumber(), ENTRY_BYTE_ORDER); - + } else if(prefix.length <= (_entryBytes.length + 3)) { - + // the prefix includes part of the page number, write to temp buffer // and copy last bytes to output buffer ByteBuffer tmp = ByteBuffer.allocate(3); @@ -2093,16 +2093,16 @@ public class IndexData { tmp.flip(); tmp.position(prefix.length - _entryBytes.length); buffer.put(tmp); - + } else { - + // since the row number would never be the same if the page number is // the same, nothing past the page number should ever be included in // the prefix. // FIXME, this could happen if page has only one row... throw new IllegalStateException("prefix should never be this long"); } - + buffer.put((byte)getRowId().getRowNumber()); } @@ -2113,7 +2113,7 @@ public class IndexData { } return sb; } - + @Override public String toString() { return entryBytesToStringBuilder( @@ -2141,7 +2141,7 @@ public class IndexData { public boolean equalsEntryBytes(Entry o) { return(BYTE_CODE_COMPARATOR.compare(_entryBytes, o._entryBytes) == 0); } - + public int compareTo(Entry other) { if (this == other) { return 0; @@ -2165,7 +2165,7 @@ public class IndexData { return typeCmp; } } - + // at this point we let the RowId decide the final result return _rowId.compareTo(other.getRowId()); } @@ -2177,7 +2177,7 @@ public class IndexData { protected Entry asNodeEntry(Integer subPageNumber) { return new NodeEntry(_entryBytes, _rowId, _type, subPageNumber); } - + } /** @@ -2200,7 +2200,7 @@ public class IndexData { super(entryBytes, rowId, type); _subPageNumber = subPageNumber; } - + /** * Read an existing node entry in from a buffer */ @@ -2222,19 +2222,19 @@ public class IndexData { public boolean isLeafEntry() { return false; } - + @Override protected int size() { // need 4 trailing bytes for the sub-page number return super.size() + 4; } - + @Override protected void write(ByteBuffer buffer, byte[] prefix) throws IOException { super.write(buffer, prefix); ByteUtil.putInt(buffer, _subPageNumber, ENTRY_BYTE_ORDER); } - + @Override public boolean equals(Object o) { return((this == o) || @@ -2250,7 +2250,7 @@ public class IndexData { .append("rowId", getRowId()) .append("subPage", _subPageNumber)) .toString(); - } + } } /** @@ -2298,14 +2298,14 @@ public class IndexData { private int getIndexModCount() { return IndexData.this._modCount; } - + /** * Returns the first entry (exclusive) as defined by this cursor. */ public Entry getFirstEntry() { return _firstPos.getEntry(); } - + /** * Returns the last entry (exclusive) as defined by this cursor. */ @@ -2320,7 +2320,7 @@ public class IndexData { public boolean isUpToDate() { return(getIndexModCount() == _lastModCount); } - + public void reset() { beforeFirst(); } @@ -2341,26 +2341,26 @@ public class IndexData { /** * Repositions the cursor so that the next row will be the first entry - * >= the given row. + * >= the given row. */ public void beforeEntry(Object[] row) throws IOException { - restorePosition(new Entry(IndexData.this.createEntryBytes(row), + restorePosition(new Entry(IndexData.this.createEntryBytes(row), RowIdImpl.FIRST_ROW_ID)); } - + /** * Repositions the cursor so that the previous row will be the first - * entry <= the given row. + * entry <= the given row. */ public void afterEntry(Object[] row) throws IOException { - restorePosition(new Entry(IndexData.this.createEntryBytes(row), + restorePosition(new Entry(IndexData.this.createEntryBytes(row), RowIdImpl.LAST_ROW_ID)); } - + /** * @return valid entry if there was a next entry, * {@code #getLastEntry} otherwise @@ -2386,7 +2386,7 @@ public class IndexData { { restorePosition(curEntry, _curPos.getEntry()); } - + /** * Restores a current and previous position for the cursor. */ @@ -2406,7 +2406,7 @@ public class IndexData { checkForModification(); } } - + /** * Gets another entry in the given direction, returning the new entry. */ @@ -2462,7 +2462,7 @@ public class IndexData { withErrorContext("Invalid entry given " + entry)); } } - + Position pos = findEntryPosition(entry); if(pos.compareTo(_lastPos) >= 0) { return _lastPos; @@ -2471,7 +2471,7 @@ public class IndexData { } return pos; } - + /** * Updates any the boundary info (_firstPos/_lastPos). */ @@ -2481,7 +2481,7 @@ public class IndexData { _firstPos = findEntryPosition(_firstPos.getEntry()); _lastPos = findEntryPosition(_lastPos.getEntry()); } - + @Override public String toString() { return CustomToStringStyle.valueBuilder(this) @@ -2489,7 +2489,7 @@ public class IndexData { .append("prevPosition", _prevPos) .toString(); } - + /** * Handles moving the cursor in a given direction. Separates cursor * logic from value storage. @@ -2500,7 +2500,7 @@ public class IndexData { public abstract Position getBeginningPosition(); public abstract Position getEndPosition(); } - + /** * Handles moving the cursor forward. */ @@ -2524,7 +2524,7 @@ public class IndexData { return _lastPos; } } - + /** * Handles moving the cursor backward. */ @@ -2569,7 +2569,7 @@ public class IndexData { { this(dataPage, idx, dataPage.getEntries().get(idx), false); } - + private Position(DataPage dataPage, int idx, Entry entry, boolean between) { _dataPage = dataPage; @@ -2581,7 +2581,7 @@ public class IndexData { public DataPage getDataPage() { return _dataPage; } - + public int getIndex() { return _idx; } @@ -2598,7 +2598,7 @@ public class IndexData { // non-between case return(_idx - 1); } - + public Entry getEntry() { return _entry; } @@ -2610,7 +2610,7 @@ public class IndexData { public boolean equalsEntry(Entry entry) { return _entry.equals(entry); } - + public int compareTo(Position other) { if(this == other) { @@ -2627,16 +2627,16 @@ public class IndexData { return idxCmp; } } - + // compare the entries. return _entry.compareTo(other._entry); } - + @Override public int hashCode() { return _entry.hashCode(); } - + @Override public boolean equals(Object o) { return((this == o) || @@ -2661,7 +2661,7 @@ public class IndexData { protected static abstract class DataPage { public abstract int getPageNumber(); - + public abstract boolean isLeaf(); public abstract void setLeaf(boolean isLeaf); @@ -2671,7 +2671,7 @@ public class IndexData { public abstract void setNextPageNumber(int pageNumber); public abstract int getChildTailPageNumber(); public abstract void setChildTailPageNumber(int pageNumber); - + public abstract int getTotalEntrySize(); public abstract void setTotalEntrySize(int totalSize); public abstract byte[] getEntryPrefix(); @@ -2688,7 +2688,7 @@ public class IndexData { public final boolean isEmpty() { return getEntries().isEmpty(); } - + public final int getCompressedEntrySize() { // when written to the index page, the entryPrefix bytes will only be // written for the first entry, so we subtract the entry prefix size @@ -2717,7 +2717,7 @@ public class IndexData { public final String toString() { List<Entry> entries = getEntries(); - String objName = + String objName = (isLeaf() ? "Leaf" : "Node") + "DataPage[" + getPageNumber() + "] " + getPrevPageNumber() + ", " + getNextPageNumber() + ", (" + getChildTailPageNumber() + ")"; @@ -2740,7 +2740,7 @@ public class IndexData { @Override public int getPageNumber() { return 0; } - + @Override public boolean isLeaf() { return true; } @Override @@ -2760,7 +2760,7 @@ public class IndexData { public int getChildTailPageNumber() { return 0; } @Override public void setChildTailPageNumber(int pageNumber) { } - + @Override public int getTotalEntrySize() { return 0; } @Override @@ -2772,7 +2772,7 @@ public class IndexData { public void setEntryPrefix(byte[] entryPrefix) { } @Override - public List<Entry> getEntries() { return Collections.emptyList(); } + public List<Entry> getEntries() { return Collections.emptyList(); } @Override public void setEntries(List<Entry> entries) { } @Override @@ -2800,7 +2800,7 @@ public class IndexData { public PendingChange getNext() { return _next; } - + /** * Completes the pending change. */ @@ -2827,7 +2827,7 @@ public class IndexData { super(next); } - public void setAddRow(Entry addEntry, DataPage dataPage, int idx, + public void setAddRow(Entry addEntry, DataPage dataPage, int idx, boolean isDupe) { _addEntry = addEntry; _addDataPage = dataPage; @@ -2843,7 +2843,7 @@ public class IndexData { public void commit() throws IOException { commitAddRow(_addEntry, _addDataPage, _addIdx, _isDupe, _oldEntry); } - + @Override public void rollback() throws IOException { _addEntry = null; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java index 0fbd231..5332432 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java @@ -25,7 +25,7 @@ import java.util.Map; import com.healthmarketscience.jackcess.CursorBuilder; import com.healthmarketscience.jackcess.Index; import com.healthmarketscience.jackcess.IndexBuilder; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexPageCache.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexPageCache.java index 4ee1882..d594c1c 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexPageCache.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexPageCache.java @@ -29,7 +29,7 @@ import java.util.Map; import java.util.RandomAccess; import static com.healthmarketscience.jackcess.impl.IndexData.*; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; /** * Manager of the index pages for a IndexData. diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java index d8a2336..466b6a4 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java @@ -35,7 +35,7 @@ import java.util.regex.Pattern; import com.healthmarketscience.jackcess.DataType; import com.healthmarketscience.jackcess.util.OleBlob; import static com.healthmarketscience.jackcess.util.OleBlob.*; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; /** * Utility code for working with OLE data. diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java index be3a249..dfcbb9d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java @@ -17,6 +17,7 @@ limitations under the License. package com.healthmarketscience.jackcess.impl; import java.io.IOException; +import java.time.LocalDateTime; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -208,7 +209,7 @@ public class PropertyMapImpl implements PropertyMap type = DataType.FLOAT; } else if(value instanceof Double) { type = DataType.DOUBLE; - } else if(value instanceof Date) { + } else if((value instanceof Date) || (value instanceof LocalDateTime)) { type = DataType.SHORT_DATE_TIME; } else if(value instanceof byte[]) { type = DataType.OLE; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/RowIdImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/RowIdImpl.java index 7bfab4a..5ce8214 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/RowIdImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/RowIdImpl.java @@ -19,7 +19,7 @@ package com.healthmarketscience.jackcess.impl; import java.io.Serializable; import com.healthmarketscience.jackcess.RowId; -import org.apache.commons.lang.builder.CompareToBuilder; +import org.apache.commons.lang3.builder.CompareToBuilder; /** diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/RowImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/RowImpl.java index ee088f4..9b9ee7a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/RowImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/RowImpl.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.LinkedHashMap; import java.util.Date; import java.math.BigDecimal; +import java.time.LocalDateTime; import com.healthmarketscience.jackcess.Row; import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey; @@ -27,8 +28,8 @@ import com.healthmarketscience.jackcess.util.OleBlob; /** - * A row of data as column->value pairs. - * </p> + * A row of data as column->value pairs. + * <br> * Note that the {@link #equals} and {@link #hashCode} methods work on the row * contents <i>only</i> (i.e. they ignore the id). * @@ -36,7 +37,7 @@ import com.healthmarketscience.jackcess.util.OleBlob; */ public class RowImpl extends LinkedHashMap<String,Object> implements Row { - private static final long serialVersionUID = 20130314L; + private static final long serialVersionUID = 20130314L; private final RowIdImpl _id; @@ -90,10 +91,15 @@ public class RowImpl extends LinkedHashMap<String,Object> implements Row return (Double)get(name); } + @SuppressWarnings("deprecation") public Date getDate(String name) { return (Date)get(name); } + public LocalDateTime getLocalDateTime(String name) { + return (LocalDateTime)get(name); + } + public byte[] getBytes(String name) { return (byte[])get(name); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java index 15a0c8c..9f6ae49 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java @@ -52,7 +52,7 @@ import com.healthmarketscience.jackcess.Table; import com.healthmarketscience.jackcess.expr.Identifier; import com.healthmarketscience.jackcess.util.ErrorHandler; import com.healthmarketscience.jackcess.util.ExportUtil; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -2086,7 +2086,7 @@ public class TableImpl implements Table, PropertyMaps.Owner } /** - * Converts a map of columnName -> columnValue to an array of row values + * Converts a map of columnName -> columnValue to an array of row values * appropriate for a call to {@link #addRow(Object...)}, where the generated * RowId will be an extra value at the end of the array. * @see ColumnImpl#RETURN_ROW_ID @@ -2110,7 +2110,7 @@ public class TableImpl implements Table, PropertyMaps.Owner } /** - * Converts a map of columnName -> columnValue to an array of row values. + * Converts a map of columnName -> columnValue to an array of row values. */ private Object[] asRow(Map<String,?> rowMap, Object defaultValue, boolean returnRowId) diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ZoneContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/ZoneContext.java new file mode 100644 index 0000000..0134e1f --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ZoneContext.java @@ -0,0 +1,32 @@ +/* +Copyright (c) 2018 James Ahlborn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.healthmarketscience.jackcess.impl; + +import java.time.ZoneId; +import java.util.TimeZone; + +/** + * Provider of zone related info for date/time conversions. + * + * @author James Ahlborn + */ +interface ZoneContext +{ + public ZoneId getZoneId(); + + public TimeZone getTimeZone(); +} diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java index 6642a69..1789e92 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.Date; import java.util.HashSet; @@ -166,7 +167,7 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment> String name = (String)getFileNameColumn().getRowValue(rawValue); String type = (String)getFileTypeColumn().getRowValue(rawValue); Integer flags = (Integer)getFileFlagsColumn().getRowValue(rawValue); - Date ts = (Date)getFileTimeStampColumn().getRowValue(rawValue); + Object ts = getFileTimeStampColumn().getRowValue(rawValue); byte[] data = (byte[])getFileDataColumn().getRowValue(rawValue); return new AttachmentImpl(id, complexValueFk, url, name, type, null, @@ -182,7 +183,7 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment> getFileNameColumn().setRowValue(row, attachment.getFileName()); getFileTypeColumn().setRowValue(row, attachment.getFileType()); getFileFlagsColumn().setRowValue(row, attachment.getFileFlags()); - getFileTimeStampColumn().setRowValue(row, attachment.getFileTimeStamp()); + getFileTimeStampColumn().setRowValue(row, attachment.getFileTimeStampObject()); getFileDataColumn().setRowValue(row, attachment.getEncodedFileData()); return row; } @@ -198,7 +199,7 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment> public static Attachment newAttachment( String url, String name, String type, byte[] data, - Date timeStamp, Integer flags) + Object timeStamp, Integer flags) { return newAttachment(INVALID_FK, url, name, type, data, timeStamp, flags); @@ -206,7 +207,7 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment> public static Attachment newAttachment( ComplexValueForeignKey complexValueFk, String url, String name, - String type, byte[] data, Date timeStamp, Integer flags) + String type, byte[] data, Object timeStamp, Integer flags) { return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type, data, timeStamp, flags, null); @@ -224,7 +225,7 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment> public static Attachment newEncodedAttachment( String url, String name, String type, byte[] encodedData, - Date timeStamp, Integer flags) + Object timeStamp, Integer flags) { return newEncodedAttachment(INVALID_FK, url, name, type, encodedData, timeStamp, flags); @@ -232,13 +233,14 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment> public static Attachment newEncodedAttachment( ComplexValueForeignKey complexValueFk, String url, String name, - String type, byte[] encodedData, Date timeStamp, Integer flags) + String type, byte[] encodedData, Object timeStamp, Integer flags) { return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type, null, timeStamp, flags, encodedData); } + @SuppressWarnings("deprecation") private static class AttachmentImpl extends ComplexValueImpl implements Attachment { @@ -246,13 +248,13 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment> private String _name; private String _type; private byte[] _data; - private Date _timeStamp; + private Object _timeStamp; private Integer _flags; private byte[] _encodedData; private AttachmentImpl(Id id, ComplexValueForeignKey complexValueFk, String url, String name, String type, byte[] data, - Date timeStamp, Integer flags, byte[] encodedData) + Object timeStamp, Integer flags, byte[] encodedData) { super(id, complexValueFk); _url = url; @@ -313,13 +315,25 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment> } public Date getFileTimeStamp() { - return _timeStamp; + return (Date)_timeStamp; } public void setFileTimeStamp(Date fileTimeStamp) { _timeStamp = fileTimeStamp; } + public LocalDateTime getFileLocalTimeStamp() { + return (LocalDateTime)_timeStamp; + } + + public void setFileLocalTimeStamp(LocalDateTime fileTimeStamp) { + _timeStamp = fileTimeStamp; + } + + public Object getFileTimeStampObject() { + return _timeStamp; + } + public Integer getFileFlags() { return _flags; } @@ -348,7 +362,7 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment> return "Attachment(" + getComplexValueForeignKey() + "," + getId() + ") " + getFileUrl() + ", " + getFileName() + ", " + getFileType() - + ", " + getFileTimeStamp() + ", " + getFileFlags() + ", " + + + ", " + getFileTimeStampObject() + ", " + getFileFlags() + ", " + dataStr; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java index a73d3ed..06c0cd7 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java @@ -17,11 +17,14 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.complex; import java.io.IOException; +import java.time.LocalDateTime; import java.util.Date; import java.util.List; import java.util.Map; import com.healthmarketscience.jackcess.Column; +import com.healthmarketscience.jackcess.Database; +import com.healthmarketscience.jackcess.DateTimeType; import com.healthmarketscience.jackcess.Row; import com.healthmarketscience.jackcess.complex.Attachment; import com.healthmarketscience.jackcess.complex.AttachmentColumnInfo; @@ -50,14 +53,15 @@ import com.healthmarketscience.jackcess.complex.VersionHistoryColumnInfo; * * @author James Ahlborn */ +@SuppressWarnings("deprecation") public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey { - private static final long serialVersionUID = 20110805L; - + private static final long serialVersionUID = 20110805L; + private transient final Column _column; private final int _value; private transient List<? extends ComplexValue> _values; - + public ComplexValueForeignKeyImpl(Column column, int value) { _column = column; _value = value; @@ -72,12 +76,12 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey public Column getColumn() { return _column; } - + @Override public ComplexDataType getComplexType() { return getComplexInfo().getType(); } - + protected ComplexColumnInfo<? extends ComplexValue> getComplexInfo() { return _column.getComplexInfo(); } @@ -85,7 +89,7 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey protected VersionHistoryColumnInfo getVersionInfo() { return (VersionHistoryColumnInfo)getComplexInfo(); } - + protected AttachmentColumnInfo getAttachmentInfo() { return (AttachmentColumnInfo)getComplexInfo(); } @@ -93,27 +97,27 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey protected MultiValueColumnInfo getMultiValueInfo() { return (MultiValueColumnInfo)getComplexInfo(); } - + protected UnsupportedColumnInfo getUnsupportedInfo() { return (UnsupportedColumnInfo)getComplexInfo(); } - + @Override public int countValues() throws IOException { return getComplexInfo().countValues(get()); } - + public List<Row> getRawValues() throws IOException { return getComplexInfo().getRawValues(get()); - } - + } + @Override public List<? extends ComplexValue> getValues() throws IOException { if(_values == null) { _values = getComplexInfo().getValues(this); } return _values; - } + } @Override @SuppressWarnings("unchecked") @@ -123,7 +127,7 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey } return (List<Version>)getValues(); } - + @Override @SuppressWarnings("unchecked") public List<Attachment> getAttachments() throws IOException { @@ -132,7 +136,7 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey } return (List<Attachment>)getValues(); } - + @Override @SuppressWarnings("unchecked") public List<SingleValue> getMultiValues() throws IOException { @@ -141,7 +145,7 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey } return (List<SingleValue>)getValues(); } - + @Override @SuppressWarnings("unchecked") public List<UnsupportedValue> getUnsupportedValues() throws IOException { @@ -150,20 +154,29 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey } return (List<UnsupportedValue>)getValues(); } - + @Override public void reset() { // discard any cached values _values = null; } - + @Override public Version addVersion(String value) throws IOException { - return addVersion(value, new Date()); + return addVersionImpl(value, now()); } - + @Override public Version addVersion(String value, Date modifiedDate) throws IOException { + return addVersionImpl(value, modifiedDate); + } + + @Override + public Version addVersion(String value, LocalDateTime modifiedDate) throws IOException { + return addVersionImpl(value, modifiedDate); + } + + private Version addVersionImpl(String value, Object modifiedDate) throws IOException { reset(); Version v = VersionHistoryColumnInfoImpl.newVersion(this, value, modifiedDate); getVersionInfo().addValue(v); @@ -172,15 +185,32 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey @Override public Attachment addAttachment(byte[] data) throws IOException { - return addAttachment(null, null, null, data, null, null); + return addAttachmentImpl(null, null, null, data, null, null); } - + @Override public Attachment addAttachment( String url, String name, String type, byte[] data, Date timeStamp, Integer flags) throws IOException { + return addAttachmentImpl(url, name, type, data, timeStamp, flags); + } + + @Override + public Attachment addAttachment( + String url, String name, String type, byte[] data, + LocalDateTime timeStamp, Integer flags) + throws IOException + { + return addAttachmentImpl(url, name, type, data, timeStamp, flags); + } + + private Attachment addAttachmentImpl( + String url, String name, String type, byte[] data, + Object timeStamp, Integer flags) + throws IOException + { reset(); Attachment a = AttachmentColumnInfoImpl.newAttachment( this, url, name, type, data, timeStamp, flags); @@ -192,36 +222,55 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey public Attachment addEncodedAttachment(byte[] encodedData) throws IOException { - return addEncodedAttachment(null, null, null, encodedData, null, null); + return addEncodedAttachmentImpl(null, null, null, encodedData, null, null); } - + @Override public Attachment addEncodedAttachment( String url, String name, String type, byte[] encodedData, Date timeStamp, Integer flags) throws IOException { + return addEncodedAttachmentImpl(url, name, type, encodedData, timeStamp, + flags); + } + + @Override + public Attachment addEncodedAttachment( + String url, String name, String type, byte[] encodedData, + LocalDateTime timeStamp, Integer flags) + throws IOException + { + return addEncodedAttachmentImpl(url, name, type, encodedData, timeStamp, + flags); + } + + private Attachment addEncodedAttachmentImpl( + String url, String name, String type, byte[] encodedData, + Object timeStamp, Integer flags) + throws IOException + { reset(); Attachment a = AttachmentColumnInfoImpl.newEncodedAttachment( this, url, name, type, encodedData, timeStamp, flags); getAttachmentInfo().addValue(a); return a; } - + @Override public Attachment updateAttachment(Attachment attachment) throws IOException { reset(); getAttachmentInfo().updateValue(attachment); return attachment; } - + @Override public Attachment deleteAttachment(Attachment attachment) throws IOException { reset(); getAttachmentInfo().deleteValue(attachment); return attachment; } - + @Override public SingleValue addMultiValue(Object value) throws IOException { reset(); @@ -229,21 +278,21 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey getMultiValueInfo().addValue(v); return v; } - + @Override public SingleValue updateMultiValue(SingleValue value) throws IOException { reset(); getMultiValueInfo().updateValue(value); return value; } - + @Override public SingleValue deleteMultiValue(SingleValue value) throws IOException { reset(); getMultiValueInfo().deleteValue(value); return value; } - + @Override public UnsupportedValue addUnsupportedValue(Map<String,?> values) throws IOException @@ -253,7 +302,7 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey getUnsupportedInfo().addValue(v); return v; } - + @Override public UnsupportedValue updateUnsupportedValue(UnsupportedValue value) throws IOException @@ -262,7 +311,7 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey getUnsupportedInfo().updateValue(value); return value; } - + @Override public UnsupportedValue deleteUnsupportedValue(UnsupportedValue value) throws IOException @@ -271,16 +320,24 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey getUnsupportedInfo().deleteValue(value); return value; } - + @Override public void deleteAllValues() throws IOException { reset(); getComplexInfo().deleteAllValues(this); } - + @Override public boolean equals(Object o) { return(super.equals(o) && (_column == ((ComplexValueForeignKeyImpl)o)._column)); } + + private Object now() { + Database db = getColumn().getDatabase(); + if(db.getDateTimeType() == DateTimeType.DATE) { + return new Date(); + } + return LocalDateTime.now(db.getZoneId()); + } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java index a64788f..14667d0 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java @@ -17,6 +17,7 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.complex; import java.io.IOException; +import java.time.LocalDateTime; import java.util.Collections; import java.util.Date; import java.util.List; @@ -42,14 +43,14 @@ import com.healthmarketscience.jackcess.impl.ColumnImpl; * * @author James Ahlborn */ -public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> +public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> implements VersionHistoryColumnInfo { private final Column _valueCol; private final Column _modifiedCol; - + public VersionHistoryColumnInfoImpl(Column column, int complexId, - Table typeObjTable, Table flatTable) + Table typeObjTable, Table flatTable) throws IOException { super(column, complexId, typeObjTable, flatTable); @@ -83,7 +84,7 @@ public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> getValueColumn().getName()); ((ColumnImpl)versionedCol).setVersionHistoryColumn((ColumnImpl)getColumn()); } - + public Column getValueColumn() { return _valueCol; } @@ -91,7 +92,7 @@ public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> public Column getModifiedDateColumn() { return _modifiedCol; } - + @Override public ComplexDataType getType() { return ComplexDataType.VERSION_HISTORY; @@ -124,7 +125,7 @@ public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> // order versions newest to oldest Collections.sort(versions); - + return versions; } @@ -133,7 +134,7 @@ public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> Row rawValue) { ComplexValue.Id id = getValueId(rawValue); String value = (String)getValueColumn().getRowValue(rawValue); - Date modifiedDate = (Date)getModifiedDateColumn().getRowValue(rawValue); + Object modifiedDate = getModifiedDateColumn().getRowValue(rawValue); return new VersionImpl(id, complexValueFk, value, modifiedDate); } @@ -142,47 +143,55 @@ public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> protected Object[] asRow(Object[] row, Version version) throws IOException { super.asRow(row, version); getValueColumn().setRowValue(row, version.getValue()); - getModifiedDateColumn().setRowValue(row, version.getModifiedDate()); + getModifiedDateColumn().setRowValue(row, version.getModifiedDateObject()); return row; } - - public static Version newVersion(String value, Date modifiedDate) { + + public static Version newVersion(String value, Object modifiedDate) { return newVersion(INVALID_FK, value, modifiedDate); } - + public static Version newVersion(ComplexValueForeignKey complexValueFk, - String value, Date modifiedDate) { + String value, Object modifiedDate) { return new VersionImpl(INVALID_ID, complexValueFk, value, modifiedDate); } - + @SuppressWarnings("deprecation") private static class VersionImpl extends ComplexValueImpl implements Version { private final String _value; - private final Date _modifiedDate; + private final Object _modifiedDate; private VersionImpl(Id id, ComplexValueForeignKey complexValueFk, - String value, Date modifiedDate) + String value, Object modifiedDate) { super(id, complexValueFk); _value = value; _modifiedDate = modifiedDate; } - + public String getValue() { return _value; } public Date getModifiedDate() { + return (Date)_modifiedDate; + } + + public LocalDateTime getModifiedLocalDate() { + return (LocalDateTime)_modifiedDate; + } + + public Object getModifiedDateObject() { return _modifiedDate; - } - + } + public int compareTo(Version o) { - Date d1 = getModifiedDate(); - Date d2 = o.getModifiedDate(); + Object d1 = getModifiedDateObject(); + Object d2 = o.getModifiedDateObject(); // sort by descending date (newest/greatest first) - int cmp = d2.compareTo(d1); + int cmp = compare(d2, d1); if(cmp != 0) { return cmp; } @@ -200,11 +209,20 @@ public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> ((id1 < id2) ? 1 : 0)); } + @SuppressWarnings("unchecked") + private static <C extends Comparable<C>> int compare(Object o1, Object o2) { + // each date/time type (Date, LocalDateTime) is mutually Comparable, so + // just silence the compiler + C c1 = (C)o1; + C c2 = (C)o2; + return c1.compareTo(c2); + } + public void update() throws IOException { throw new UnsupportedOperationException( "This column does not support value updates"); } - + public void delete() throws IOException { throw new UnsupportedOperationException( "This column does not support value deletes"); @@ -214,8 +232,8 @@ public class VersionHistoryColumnInfoImpl extends ComplexColumnInfoImpl<Version> public String toString() { return "Version(" + getComplexValueForeignKey() + "," + getId() + ") " + - getModifiedDate() + ", " + getValue(); - } + getModifiedDateObject() + ", " + getValue(); + } } - + } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java index d527c69..1e99e64 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java @@ -17,7 +17,7 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; -import java.util.Date; +import java.time.LocalDateTime; import com.healthmarketscience.jackcess.expr.LocaleContext; import com.healthmarketscience.jackcess.expr.Value; @@ -60,8 +60,8 @@ public abstract class BaseDelayedValue implements Value return getDelegate().getAsString(ctx); } - public Date getAsDateTime(LocaleContext ctx) { - return getDelegate().getAsDateTime(ctx); + public LocalDateTime getAsLocalDateTime(LocaleContext ctx) { + return getDelegate().getAsLocalDateTime(ctx); } public Value getAsDateTimeValue(LocaleContext ctx) { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java index 299cd2a..cae689d 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java @@ -44,7 +44,7 @@ public abstract class BaseNumericValue extends BaseValue @Override public Value getAsDateTimeValue(LocaleContext ctx) { Value dateValue = DefaultDateFunctions.numberToDateValue( - ctx, getNumber().doubleValue()); + getNumber().doubleValue()); if(dateValue == null) { throw invalidConversion(Value.Type.DATE_TIME); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java index 2b172d3..35e6ccf 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java @@ -17,11 +17,11 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; -import java.util.Date; +import java.time.LocalDateTime; -import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.expr.EvalException; import com.healthmarketscience.jackcess.expr.LocaleContext; +import com.healthmarketscience.jackcess.expr.Value; /** * @@ -41,8 +41,8 @@ public abstract class BaseValue implements Value throw invalidConversion(Type.STRING); } - public Date getAsDateTime(LocaleContext ctx) { - return (Date)getAsDateTimeValue(ctx).get(); + public LocalDateTime getAsLocalDateTime(LocaleContext ctx) { + return (LocalDateTime)getAsDateTimeValue(ctx).get(); } public Value getAsDateTimeValue(LocaleContext ctx) { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java index 5131a93..5f63dad 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java @@ -76,7 +76,7 @@ public class BuiltinOperators case DATE_TIME: // dates/times get converted to date doubles for arithmetic double result = -param1.getAsDouble(ctx); - return toDateValue(ctx, mathType, result); + return toDateValueIfPossible(mathType, result); case LONG: return toValue(-param1.getAsLongInt(ctx)); case DOUBLE: @@ -108,7 +108,7 @@ public class BuiltinOperators case DATE_TIME: // dates/times get converted to date doubles for arithmetic double result = param1.getAsDouble(ctx) + param2.getAsDouble(ctx); - return toDateValue(ctx, mathType, result); + return toDateValueIfPossible(mathType, result); case LONG: return toValue(param1.getAsLongInt(ctx) + param2.getAsLongInt(ctx)); case DOUBLE: @@ -138,7 +138,7 @@ public class BuiltinOperators case DATE_TIME: // dates/times get converted to date doubles for arithmetic double result = param1.getAsDouble(ctx) - param2.getAsDouble(ctx); - return toDateValue(ctx, mathType, result); + return toDateValueIfPossible(mathType, result); case LONG: return toValue(param1.getAsLongInt(ctx) - param2.getAsLongInt(ctx)); case DOUBLE: diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java index e2de36d..f9d1e7b 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java @@ -17,11 +17,11 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; -import java.util.Date; +import java.time.LocalDateTime; -import com.healthmarketscience.jackcess.impl.ColumnImpl; import com.healthmarketscience.jackcess.expr.LocaleContext; import com.healthmarketscience.jackcess.expr.Value; +import com.healthmarketscience.jackcess.impl.ColumnImpl; /** * @@ -30,9 +30,9 @@ import com.healthmarketscience.jackcess.expr.Value; public class DateTimeValue extends BaseValue { private final Type _type; - private final Date _val; + private final LocalDateTime _val; - public DateTimeValue(Type type, Date val) { + public DateTimeValue(Type type, LocalDateTime val) { if(!type.isTemporal()) { throw new IllegalArgumentException("invalid date/time type"); } @@ -49,7 +49,7 @@ public class DateTimeValue extends BaseValue } protected Double getNumber(LocaleContext ctx) { - return ColumnImpl.toDateDouble(_val, ctx.getCalendar()); + return ColumnImpl.toDateDouble(_val); } @Override @@ -64,7 +64,7 @@ public class DateTimeValue extends BaseValue } @Override - public Date getAsDateTime(LocaleContext ctx) { + public LocalDateTime getAsLocalDateTime(LocaleContext ctx) { return _val; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java index a19ab0a..2ac67d2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java @@ -17,11 +17,20 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; -import java.math.BigDecimal; -import java.text.DateFormat; -import java.text.DateFormatSymbols; -import java.util.Calendar; -import java.util.Date; +import java.time.DateTimeException; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.MonthDay; +import java.time.Year; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.WeekFields; import com.healthmarketscience.jackcess.expr.EvalContext; import com.healthmarketscience.jackcess.expr.EvalException; @@ -44,12 +53,6 @@ public class DefaultDateFunctions // max, valid, recognizable date: December 31, 9999 A.D. 23:59:59 private static final double MAX_DATE = 2958465.999988426d; - private static final long SECONDS_PER_DAY = 24L * 60L * 60L; - private static final double DSECONDS_PER_DAY = SECONDS_PER_DAY; - - private static final long SECONDS_PER_HOUR = 60L * 60L; - private static final long SECONDS_PER_MINUTE = 60L; - private static final String INTV_YEAR = "yyyy"; private static final String INTV_QUARTER = "q"; private static final String INTV_MONTH = "m"; @@ -61,7 +64,8 @@ public class DefaultDateFunctions private static final String INTV_MINUTE = "n"; private static final String INTV_SECOND = "s"; - private enum WeekOpType { GET_WEEK, GET_NUM_WEEKS } + private static final WeekFields SUNDAY_FIRST = + WeekFields.of(DayOfWeek.SUNDAY, 1); private DefaultDateFunctions() {} @@ -72,8 +76,7 @@ public class DefaultDateFunctions public static final Function DATE = registerFunc(new Func0("Date") { @Override protected Value eval0(EvalContext ctx) { - double dd = dateOnly(currentTimeDouble(ctx)); - return ValueSupport.toDateValue(ctx, Value.Type.DATE, dd); + return ValueSupport.toValue(LocalDate.now()); } }); @@ -84,8 +87,7 @@ public class DefaultDateFunctions if(dv.getType() == Value.Type.DATE) { return dv; } - double dd = dateOnly(dv.getAsDouble(ctx)); - return ValueSupport.toDateValue(ctx, Value.Type.DATE, dd); + return ValueSupport.toValue(dv.getAsLocalDateTime(ctx).toLocalDate()); } }); @@ -101,15 +103,11 @@ public class DefaultDateFunctions year += ((year <= 29) ? 2000 : 1900); } - Calendar cal = ctx.getCalendar(); - cal.clear(); + // we have to construct incrementatlly to handle out of range values + LocalDate ld = LocalDate.of(year,1,1).plusMonths(month - 1) + .plusDays(day - 1); - cal.set(Calendar.YEAR, year); - // convert to 0 based value - cal.set(Calendar.MONTH, month - 1); - cal.set(Calendar.DAY_OF_MONTH, day); - - return ValueSupport.toValue(Value.Type.DATE, cal.getTime()); + return ValueSupport.toValue(ld); } }); @@ -127,27 +125,27 @@ public class DefaultDateFunctions String intv = params[0].getAsString(ctx).trim(); int result = -1; if(intv.equalsIgnoreCase(INTV_YEAR)) { - result = nonNullToCalendarField(ctx, param2, Calendar.YEAR); + result = param2.getAsLocalDateTime(ctx).getYear(); } else if(intv.equalsIgnoreCase(INTV_QUARTER)) { - result = getQuarter(nonNullToCalendar(ctx, param2)); + result = getQuarter(param2.getAsLocalDateTime(ctx)); } else if(intv.equalsIgnoreCase(INTV_MONTH)) { - // convert from 0 based to 1 based value - result = nonNullToCalendarField(ctx, param2, Calendar.MONTH) + 1; + result = param2.getAsLocalDateTime(ctx).getMonthValue(); } else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR)) { - result = nonNullToCalendarField(ctx, param2, Calendar.DAY_OF_YEAR); + result = param2.getAsLocalDateTime(ctx).getDayOfYear(); } else if(intv.equalsIgnoreCase(INTV_DAY)) { - result = nonNullToCalendarField(ctx, param2, Calendar.DAY_OF_MONTH); + result = param2.getAsLocalDateTime(ctx).getDayOfMonth(); } else if(intv.equalsIgnoreCase(INTV_WEEKDAY)) { - int dayOfWeek = nonNullToCalendarField(ctx, param2, Calendar.DAY_OF_WEEK); + int dayOfWeek = param2.getAsLocalDateTime(ctx) + .get(SUNDAY_FIRST.dayOfWeek()); result = dayOfWeekToWeekDay(dayOfWeek, firstDay); } else if(intv.equalsIgnoreCase(INTV_WEEK)) { result = weekOfYear(ctx, param2, firstDay, firstWeekType); } else if(intv.equalsIgnoreCase(INTV_HOUR)) { - result = nonNullToCalendarField(ctx, param2, Calendar.HOUR_OF_DAY); + result = param2.getAsLocalDateTime(ctx).getHour(); } else if(intv.equalsIgnoreCase(INTV_MINUTE)) { - result = nonNullToCalendarField(ctx, param2, Calendar.MINUTE); + result = param2.getAsLocalDateTime(ctx).getMinute(); } else if(intv.equalsIgnoreCase(INTV_SECOND)) { - result = nonNullToCalendarField(ctx, param2, Calendar.SECOND); + result = param2.getAsLocalDateTime(ctx).getSecond(); } else { throw new EvalException("Invalid interval " + intv); } @@ -167,33 +165,31 @@ public class DefaultDateFunctions String intv = param1.getAsString(ctx).trim(); int val = param2.getAsLongInt(ctx); - Calendar cal = nonNullToCalendar(ctx, param3); + LocalDateTime ldt = param3.getAsLocalDateTime(ctx); if(intv.equalsIgnoreCase(INTV_YEAR)) { - cal.add(Calendar.YEAR, val); + ldt = ldt.plus(val, ChronoUnit.YEARS); } else if(intv.equalsIgnoreCase(INTV_QUARTER)) { - cal.add(Calendar.MONTH, val * 3); + ldt = ldt.plus(val * 3, ChronoUnit.MONTHS); } else if(intv.equalsIgnoreCase(INTV_MONTH)) { - cal.add(Calendar.MONTH, val); - } else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR)) { - cal.add(Calendar.DAY_OF_YEAR, val); - } else if(intv.equalsIgnoreCase(INTV_DAY)) { - cal.add(Calendar.DAY_OF_YEAR, val); - } else if(intv.equalsIgnoreCase(INTV_WEEKDAY)) { - cal.add(Calendar.DAY_OF_WEEK, val); + ldt = ldt.plus(val, ChronoUnit.MONTHS); + } else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR) || + intv.equalsIgnoreCase(INTV_DAY) || + intv.equalsIgnoreCase(INTV_WEEKDAY)) { + ldt = ldt.plus(val, ChronoUnit.DAYS); } else if(intv.equalsIgnoreCase(INTV_WEEK)) { - cal.add(Calendar.WEEK_OF_YEAR, val); + ldt = ldt.plus(val, ChronoUnit.WEEKS); } else if(intv.equalsIgnoreCase(INTV_HOUR)) { - cal.add(Calendar.HOUR, val); + ldt = ldt.plus(val, ChronoUnit.HOURS); } else if(intv.equalsIgnoreCase(INTV_MINUTE)) { - cal.add(Calendar.MINUTE, val); + ldt = ldt.plus(val, ChronoUnit.MINUTES); } else if(intv.equalsIgnoreCase(INTV_SECOND)) { - cal.add(Calendar.SECOND, val); + ldt = ldt.plus(val, ChronoUnit.SECONDS); } else { throw new EvalException("Invalid interval " + intv); } - return ValueSupport.toValue(cal); + return ValueSupport.toValue(ldt); } }); @@ -212,14 +208,14 @@ public class DefaultDateFunctions String intv = params[0].getAsString(ctx).trim(); - Calendar cal1 = nonNullToCalendar(ctx, param2); - Calendar cal2 = nonNullToCalendar(ctx, param3); + LocalDateTime ldt1 = param2.getAsLocalDateTime(ctx); + LocalDateTime ldt2 = param3.getAsLocalDateTime(ctx); int sign = 1; - if(cal1.after(cal2)) { - Calendar tmp = cal1; - cal1 = cal2; - cal2 = tmp; + if(ldt1.isAfter(ldt2)) { + LocalDateTime tmp = ldt1; + ldt1 = ldt2; + ldt2 = tmp; sign = -1; } @@ -229,22 +225,22 @@ public class DefaultDateFunctions int result = -1; if(intv.equalsIgnoreCase(INTV_YEAR)) { - result = cal2.get(Calendar.YEAR) - cal1.get(Calendar.YEAR); + result = ldt2.getYear() - ldt1.getYear(); } else if(intv.equalsIgnoreCase(INTV_QUARTER)) { - int y1 = cal1.get(Calendar.YEAR); - int q1 = getQuarter(cal1); - int y2 = cal2.get(Calendar.YEAR); - int q2 = getQuarter(cal2); + int y1 = ldt1.getYear(); + int q1 = getQuarter(ldt1); + int y2 = ldt2.getYear(); + int q2 = getQuarter(ldt2); while(y2 > y1) { q2 += 4; --y2; } result = q2 - q1; } else if(intv.equalsIgnoreCase(INTV_MONTH)) { - int y1 = cal1.get(Calendar.YEAR); - int m1 = cal1.get(Calendar.MONTH); - int y2 = cal2.get(Calendar.YEAR); - int m2 = cal2.get(Calendar.MONTH); + int y1 = ldt1.getYear(); + int m1 = ldt1.getMonthValue(); + int y2 = ldt2.getYear(); + int m2 = ldt2.getMonthValue(); while(y2 > y1) { m2 += 12; --y2; @@ -252,30 +248,30 @@ public class DefaultDateFunctions result = m2 - m1; } else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR) || intv.equalsIgnoreCase(INTV_DAY)) { - result = getDayDiff(cal1, cal2); + result = getDayDiff(ldt1, ldt2); } else if(intv.equalsIgnoreCase(INTV_WEEKDAY)) { // this calulates number of 7 day periods between two dates - result = getDayDiff(cal1, cal2) / 7; + result = getDayDiff(ldt1, ldt2) / 7; } else if(intv.equalsIgnoreCase(INTV_WEEK)) { // this counts number of "week of year" intervals between two dates - int w1 = weekOfYear(cal1, firstDay, firstWeekType); - int y1 = getWeekOfYearYear(cal1, w1); - int w2 = weekOfYear(cal2, firstDay, firstWeekType); - int y2 = getWeekOfYearYear(cal2, w2); + WeekFields weekFields = weekFields(firstDay, firstWeekType); + int w1 = ldt1.get(weekFields.weekOfWeekBasedYear()); + int y1 = ldt1.get(weekFields.weekBasedYear()); + int w2 = ldt2.get(weekFields.weekOfWeekBasedYear()); + int y2 = ldt2.get(weekFields.weekBasedYear()); while(y2 > y1) { - cal2.add(Calendar.YEAR, -1); - w2 += weeksInYear(cal2, firstDay, firstWeekType); - y2 = cal2.get(Calendar.YEAR); + --y2; + w2 += weeksInYear(y2, weekFields); } result = w2 - w1; } else if(intv.equalsIgnoreCase(INTV_HOUR)) { - result = getHourDiff(cal1, cal2); + result = getHourDiff(ldt1, ldt2); } else if(intv.equalsIgnoreCase(INTV_MINUTE)) { - result = getMinuteDiff(cal1, cal2); + result = getMinuteDiff(ldt1, ldt2); } else if(intv.equalsIgnoreCase(INTV_SECOND)) { - int s1 = cal1.get(Calendar.SECOND); - int s2 = cal2.get(Calendar.SECOND); - int minuteDiff = getMinuteDiff(cal1, cal2); + int s1 = ldt1.getSecond(); + int s2 = ldt2.getSecond(); + int minuteDiff = getMinuteDiff(ldt1, ldt2); result = (s2 + (60 * minuteDiff)) - s1; } else { throw new EvalException("Invalid interval " + intv); @@ -288,15 +284,15 @@ public class DefaultDateFunctions public static final Function NOW = registerFunc(new Func0("Now") { @Override protected Value eval0(EvalContext ctx) { - return ValueSupport.toValue(Value.Type.DATE_TIME, new Date()); + return ValueSupport.toValue(Value.Type.DATE_TIME, + LocalDateTime.now(ctx.getZoneId())); } }); public static final Function TIME = registerFunc(new Func0("Time") { @Override protected Value eval0(EvalContext ctx) { - double dd = timeOnly(currentTimeDouble(ctx)); - return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd); + return ValueSupport.toValue(LocalTime.now(ctx.getZoneId())); } }); @@ -307,15 +303,15 @@ public class DefaultDateFunctions if(dv.getType() == Value.Type.TIME) { return dv; } - double dd = timeOnly(dv.getAsDouble(ctx)); - return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd); + return ValueSupport.toValue(dv.getAsLocalDateTime(ctx).toLocalTime()); } }); public static final Function TIMER = registerFunc(new Func0("Timer") { @Override protected Value eval0(EvalContext ctx) { - double dd = timeOnly(currentTimeDouble(ctx)) * DSECONDS_PER_DAY; + double dd = LocalTime.now(ctx.getZoneId()) + .get(ChronoField.MILLI_OF_DAY) / 1000d; return ValueSupport.toValue(dd); } }); @@ -327,59 +323,46 @@ public class DefaultDateFunctions int minutes = param2.getAsLongInt(ctx); int seconds = param3.getAsLongInt(ctx); - long totalSeconds = (hours * SECONDS_PER_HOUR) + - (minutes * SECONDS_PER_MINUTE) + seconds; - if(totalSeconds < 0L) { - do { - totalSeconds += SECONDS_PER_DAY; - } while(totalSeconds < 0L); - } else if(totalSeconds > SECONDS_PER_DAY) { - totalSeconds %= SECONDS_PER_DAY; - } + // we have to construct incrementatlly to handle out of range values + LocalTime lt = ColumnImpl.BASE_LT.plusHours(hours).plusMinutes(minutes) + .plusSeconds(seconds); - double dd = totalSeconds / DSECONDS_PER_DAY; - return ValueSupport.toDateValue(ctx, Value.Type.TIME, dd); + return ValueSupport.toValue(lt); } }); public static final Function HOUR = registerFunc(new Func1NullIsNull("Hour") { @Override protected Value eval1(EvalContext ctx, Value param1) { - return ValueSupport.toValue( - nonNullToCalendarField(ctx, param1, Calendar.HOUR_OF_DAY)); + return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getHour()); } }); public static final Function MINUTE = registerFunc(new Func1NullIsNull("Minute") { @Override protected Value eval1(EvalContext ctx, Value param1) { - return ValueSupport.toValue( - nonNullToCalendarField(ctx, param1, Calendar.MINUTE)); + return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getMinute()); } }); public static final Function SECOND = registerFunc(new Func1NullIsNull("Second") { @Override protected Value eval1(EvalContext ctx, Value param1) { - return ValueSupport.toValue( - nonNullToCalendarField(ctx, param1, Calendar.SECOND)); + return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getSecond()); } }); public static final Function YEAR = registerFunc(new Func1NullIsNull("Year") { @Override protected Value eval1(EvalContext ctx, Value param1) { - return ValueSupport.toValue( - nonNullToCalendarField(ctx, param1, Calendar.YEAR)); + return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getYear()); } }); public static final Function MONTH = registerFunc(new Func1NullIsNull("Month") { @Override protected Value eval1(EvalContext ctx, Value param1) { - // convert from 0 based to 1 based value - return ValueSupport.toValue( - nonNullToCalendarField(ctx, param1, Calendar.MONTH) + 1); + return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getMonthValue()); } }); @@ -390,16 +373,12 @@ public class DefaultDateFunctions if(param1.isNull()) { return ValueSupport.NULL_VAL; } - // convert from 1 based to 0 based value - int month = param1.getAsLongInt(ctx) - 1; - - boolean abbreviate = getOptionalBooleanParam(ctx, params, 1); + Month month = Month.of(param1.getAsLongInt(ctx)); - DateFormatSymbols syms = ctx.getTemporalConfig().getDateFormatSymbols(); - String[] monthNames = (abbreviate ? - syms.getShortMonths() : syms.getMonths()); - // note, the array is 1 based - return ValueSupport.toValue(monthNames[month]); + TextStyle textStyle = getTextStyle(ctx, params, 1); + String monthName = month.getDisplayName( + textStyle, ctx.getTemporalConfig().getLocale()); + return ValueSupport.toValue(monthName); } }); @@ -407,7 +386,7 @@ public class DefaultDateFunctions @Override protected Value eval1(EvalContext ctx, Value param1) { return ValueSupport.toValue( - nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_MONTH)); + param1.getAsLocalDateTime(ctx).getDayOfMonth()); } }); @@ -418,7 +397,8 @@ public class DefaultDateFunctions if(param1.isNull()) { return ValueSupport.NULL_VAL; } - int dayOfWeek = nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_WEEK); + int dayOfWeek = param1.getAsLocalDateTime(ctx) + .get(SUNDAY_FIRST.dayOfWeek()); int firstDay = getFirstDayParam(ctx, params, 1); @@ -435,31 +415,17 @@ public class DefaultDateFunctions } int weekday = param1.getAsLongInt(ctx); - boolean abbreviate = getOptionalBooleanParam(ctx, params, 1); + TextStyle textStyle = getTextStyle(ctx, params, 1); int firstDay = getFirstDayParam(ctx, params, 2); int dayOfWeek = weekDayToDayOfWeek(weekday, firstDay); - - DateFormatSymbols syms = ctx.getTemporalConfig().getDateFormatSymbols(); - String[] weekdayNames = (abbreviate ? - syms.getShortWeekdays() : syms.getWeekdays()); - // note, the array is 1 based - return ValueSupport.toValue(weekdayNames[dayOfWeek]); + String weekdayName = dayOfWeek(dayOfWeek).getDisplayName( + textStyle, ctx.getTemporalConfig().getLocale()); + return ValueSupport.toValue(weekdayName); } }); - private static int nonNullToCalendarField(EvalContext ctx, Value param, - int field) { - return nonNullToCalendar(ctx, param).get(field); - } - - private static Calendar nonNullToCalendar(EvalContext ctx, Value param) { - Calendar cal = ctx.getCalendar(); - cal.setTime(param.getAsDateTime(ctx)); - return cal; - } - static Value stringToDateValue(LocaleContext ctx, String valStr) { // see if we can coerce to date/time TemporalConfig.Type valTempType = ExpressionTokenizer.determineDateType( @@ -467,26 +433,31 @@ public class DefaultDateFunctions if(valTempType != null) { - DateFormat parseDf = ExpressionTokenizer.createParseDateTimeFormat( - valTempType, ctx); + DateTimeFormatter parseDf = ctx.createDateFormatter( + ctx.getTemporalConfig().getDateTimeFormat(valTempType)); try { - Date dateVal = ExpressionTokenizer.parseComplete(parseDf, valStr); - return ValueSupport.toValue(valTempType.getValueType(), dateVal); - } catch(java.text.ParseException pe) { + TemporalAccessor parsedInfo = parseDf.parse(valStr); + LocalDate ld = ColumnImpl.BASE_LD; if(valTempType.includesDate()) { - // the date may not include a year value, in which case it means - // to use the "current" year. see if this is an implicit year date - parseDf = ExpressionTokenizer.createParseImplicitYearDateTimeFormat( - valTempType, ctx); - try { - Date dateVal = ExpressionTokenizer.parseComplete(parseDf, valStr); - return ValueSupport.toValue(valTempType.getValueType(), dateVal); - } catch(java.text.ParseException pe2) { - // guess not, continue on to failure + // the year may not be explicitly specified + if(parsedInfo.isSupported(ChronoField.YEAR)) { + ld = LocalDate.from(parsedInfo); + } else { + ld = MonthDay.from(parsedInfo).atYear( + Year.now(ctx.getZoneId()).getValue()); } } + + LocalTime lt = ColumnImpl.BASE_LT; + if(valTempType.includesTime()) { + lt = LocalTime.from(parsedInfo); + } + + return ValueSupport.toValue(LocalDateTime.of(ld, lt)); + } catch(DateTimeException de) { + // note a valid date/time } } @@ -494,35 +465,18 @@ public class DefaultDateFunctions return null; } - static Value numberToDateValue(LocaleContext ctx, double dd) { - if((dd < MIN_DATE) || (dd > MAX_DATE)) { + static boolean isValidDateDouble(double dd) { + return ((dd >= MIN_DATE) && (dd <= MAX_DATE)); + } + + static Value numberToDateValue(double dd) { + if(!isValidDateDouble(dd)) { // outside valid date range return null; } - boolean hasDate = (dateOnly(dd) != 0.0d); - boolean hasTime = (timeOnly(dd) != 0.0d); - - Value.Type type = (hasDate ? (hasTime ? Value.Type.DATE_TIME : - Value.Type.DATE) : - Value.Type.TIME); - return ValueSupport.toDateValue(ctx, type, dd); - } - - private static double dateOnly(double dd) { - // the integral part of the date/time double is the date value. discard - // the fractional portion - return (long)dd; - } - - private static double timeOnly(double dd) { - // the fractional part of the date/time double is the time value. discard - // the integral portion - return new BigDecimal(dd).remainder(BigDecimal.ONE).doubleValue(); - } - - private static double currentTimeDouble(LocaleContext ctx) { - return ColumnImpl.toDateDouble(System.currentTimeMillis(), ctx.getCalendar()); + LocalDateTime ldt = ColumnImpl.ldtFromLocalDateDouble(dd); + return ValueSupport.toValue(ldt); } private static int dayOfWeekToWeekDay(int day, int firstDay) { @@ -551,114 +505,85 @@ public class DefaultDateFunctions return getOptionalIntParam(ctx, params, idx, 1, 0); } - private static int weekOfYear(EvalContext ctx, Value param, int firstDay, - int firstWeekType) { - return doWeekOp(nonNullToCalendar(ctx, param), firstDay, firstWeekType, - WeekOpType.GET_WEEK); - } + private static WeekFields weekFields(int firstDay, int firstWeekType) { + + int minDays = 1; + switch(firstWeekType) { + case 1: + // vbUseSystem 0 + // vbFirstJan1 1 (default) + break; + case 2: + // vbFirstFourDays 2 + minDays = 4; + break; + case 3: + // vbFirstFullWeek 3 + minDays = 7; + break; + default: + throw new EvalException("Invalid first week of year type " + + firstWeekType); + } - private static int weekOfYear(Calendar cal, int firstDay, int firstWeekType) { - return doWeekOp(cal, firstDay, firstWeekType, WeekOpType.GET_WEEK); + return WeekFields.of(dayOfWeek(firstDay), minDays); } - private static int weeksInYear(Calendar cal, int firstDay, int firstWeekType) { - return doWeekOp(cal, firstDay, firstWeekType, WeekOpType.GET_NUM_WEEKS); + private static DayOfWeek dayOfWeek(int dayOfWeek) { + return DayOfWeek.SUNDAY.plus(dayOfWeek - 1); } - private static int doWeekOp(Calendar cal, int firstDay, int firstWeekType, - WeekOpType opType) { - // need to mess with some calendar settings, but they need to be restored - // when done because the Calendar instance may be shared - int origFirstDay = cal.getFirstDayOfWeek(); - int origMinDays = cal.getMinimalDaysInFirstWeek(); - try { - - int minDays = 1; - switch(firstWeekType) { - case 1: - // vbUseSystem 0 - // vbFirstJan1 1 (default) - break; - case 2: - // vbFirstFourDays 2 - minDays = 4; - break; - case 3: - // vbFirstFullWeek 3 - minDays = 7; - break; - default: - throw new EvalException("Invalid first week of year type " + - firstWeekType); - } - - cal.setFirstDayOfWeek(firstDay); - cal.setMinimalDaysInFirstWeek(minDays); + private static TextStyle getTextStyle(EvalContext ctx, Value[] params, + int idx) { + boolean abbreviate = getOptionalBooleanParam(ctx, params, 1); + return (abbreviate ? TextStyle.SHORT : TextStyle.FULL); + } - switch(opType) { - case GET_WEEK: - return cal.get(Calendar.WEEK_OF_YEAR); - case GET_NUM_WEEKS: - return cal.getActualMaximum(Calendar.WEEK_OF_YEAR); - default: - throw new RuntimeException("Unknown op type " + opType); - } + private static int weekOfYear(EvalContext ctx, Value param, int firstDay, + int firstWeekType) { + return weekOfYear(param.getAsLocalDateTime(ctx), firstDay, firstWeekType); + } - } finally { - cal.setFirstDayOfWeek(origFirstDay); - cal.setMinimalDaysInFirstWeek(origMinDays); - } + private static int weekOfYear(LocalDateTime ldt, int firstDay, + int firstWeekType) { + WeekFields weekFields = weekFields(firstDay, firstWeekType); + return ldt.get(weekFields.weekOfWeekBasedYear()); } - private static int getQuarter(Calendar cal) { - // month is 0 based - int month = cal.get(Calendar.MONTH); - return (month / 3) + 1; + private static int weeksInYear(int year, WeekFields weekFields) { + return (int)LocalDate.of(year,2,1).range(weekFields.weekOfWeekBasedYear()) + .getMaximum(); } - private static int getWeekOfYearYear(Calendar cal, int weekOfYear) { - // the "week of year" gets weird at the beginning/end of the year. - // e.g. 12/31 might be int the first week of the next year, and 1/1 might - // be in the last week of the previous year. we need to detect this and - // adjust the intervals accordingly - if(cal.get(Calendar.MONTH) == Calendar.JANUARY) { - if(weekOfYear >= 52) { - // this week of year is effectively for the previous year - cal.add(Calendar.YEAR, -1); - } - } else { - if(weekOfYear == 1) { - // this week of year is effectively for next year - cal.add(Calendar.YEAR, 1); - } - } - return cal.get(Calendar.YEAR); + private static int getQuarter(LocalDateTime ldt) { + int month = ldt.getMonthValue() - 1; + return (month / 3) + 1; } - private static int getDayDiff(Calendar cal1, Calendar cal2) { - int y1 = cal1.get(Calendar.YEAR); - int d1 = cal1.get(Calendar.DAY_OF_YEAR); - int y2 = cal2.get(Calendar.YEAR); - int d2 = cal2.get(Calendar.DAY_OF_YEAR); - while(y2 > y1) { - cal2.add(Calendar.YEAR, -1); - d2 += cal2.getActualMaximum(Calendar.DAY_OF_YEAR); - y2 = cal2.get(Calendar.YEAR); + private static int getDayDiff(LocalDateTime ldt1, LocalDateTime ldt2) { + int y1 = ldt1.getYear(); + int d1 = ldt1.getDayOfYear(); + int y2 = ldt2.getYear(); + int d2 = ldt2.getDayOfYear(); + while(y2 > y1) { + ldt2 = ldt2.minusYears(1); + d2 += ldt2.range(ChronoField.DAY_OF_YEAR).getMaximum(); + y2 = ldt2.getYear(); } return d2 - d1; } - private static int getHourDiff(Calendar cal1, Calendar cal2) { - int h1 = cal1.get(Calendar.HOUR_OF_DAY); - int h2 = cal2.get(Calendar.HOUR_OF_DAY); - int dayDiff = getDayDiff(cal1, cal2); + private static int getHourDiff(LocalDateTime ldt1, LocalDateTime ldt2) { + int h1 = ldt1.getHour(); + int h2 = ldt2.getHour(); + int dayDiff = getDayDiff(ldt1, ldt2); return (h2 + (24 * dayDiff)) - h1; } - private static int getMinuteDiff(Calendar cal1, Calendar cal2) { - int m1 = cal1.get(Calendar.MINUTE); - int m2 = cal2.get(Calendar.MINUTE); - int hourDiff = getHourDiff(cal1, cal2); + private static int getMinuteDiff(LocalDateTime ldt1, LocalDateTime ldt2) { + int m1 = ldt1.getMinute(); + int m2 = ldt2.getMinute(); + int hourDiff = getHourDiff(ldt1, ldt2); return (m2 + (60 * hourDiff)) - m1; } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java index 29c0f71..763d1d5 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java @@ -18,10 +18,9 @@ package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; import java.math.BigInteger; -import java.text.DateFormat; import java.text.DecimalFormat; -import java.util.Calendar; -import java.util.Date; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -310,16 +309,14 @@ public class DefaultFunctions return ValueSupport.NULL_VAL; } - Date d = param1.getAsDateTime(ctx); + LocalDateTime ldt = param1.getAsLocalDateTime(ctx); int fmtType = getOptionalIntParam(ctx, params, 1, 0); TemporalConfig.Type tempType = null; switch(fmtType) { case 0: // vbGeneralDate - Calendar cal = ctx.getCalendar(); - cal.setTime(d); - Value.Type valType = ValueSupport.getDateTimeType(cal); + Value.Type valType = ValueSupport.getDateTimeType(ldt); switch(valType) { case DATE: tempType = TemporalConfig.Type.SHORT_DATE; @@ -351,9 +348,9 @@ public class DefaultFunctions throw new EvalException("Unknown format " + fmtType); } - DateFormat sdf = ctx.createDateFormat( + DateTimeFormatter dtf = ctx.createDateFormatter( ctx.getTemporalConfig().getDateTimeFormat(tempType)); - return ValueSupport.toValue(sdf.format(d)); + return ValueSupport.toValue(dtf.format(ldt)); } }); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java index 1ec08db..70eb5a9 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java @@ -49,7 +49,7 @@ public class DefaultNumberFunctions case DATE_TIME: // dates/times get converted to date doubles for arithmetic double result = Math.abs(param1.getAsDouble(ctx)); - return ValueSupport.toDateValue(ctx, mathType, result); + return ValueSupport.toDateValueIfPossible(mathType, result); case LONG: return ValueSupport.toValue(Math.abs(param1.getAsLongInt(ctx))); case DOUBLE: diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java index 74856aa..77edf80 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java @@ -13,7 +13,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; @@ -23,7 +22,6 @@ import com.healthmarketscience.jackcess.expr.EvalException; import com.healthmarketscience.jackcess.expr.Function; import com.healthmarketscience.jackcess.expr.LocaleContext; import com.healthmarketscience.jackcess.expr.Value; -import org.apache.commons.lang.WordUtils; import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*; import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*; @@ -360,6 +358,7 @@ public class DefaultTextFunctions } }); + @SuppressWarnings("deprecation") public static final Function STRCONV = registerStringFunc(new FuncVar("StrConv", 2, 3) { @Override protected Value evalVar(EvalContext ctx, Value[] params) { @@ -387,7 +386,8 @@ public class DefaultTextFunctions break; case 3: // vbProperCase - str = WordUtils.capitalize(str.toLowerCase()); + str = org.apache.commons.lang3.text.WordUtils.capitalize( + str.toLowerCase()); break; default: // do nothing diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java index cc0fca4..4db436a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java @@ -17,26 +17,27 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; -import java.text.DateFormat; -import java.text.FieldPosition; -import java.text.ParsePosition; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; -import java.util.Date; import java.util.EnumMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TimeZone; import static com.healthmarketscience.jackcess.impl.expr.Expressionator.*; import com.healthmarketscience.jackcess.expr.LocaleContext; import com.healthmarketscience.jackcess.expr.ParseException; import com.healthmarketscience.jackcess.expr.TemporalConfig; import com.healthmarketscience.jackcess.expr.Value; +import com.healthmarketscience.jackcess.impl.ColumnImpl; /** @@ -53,15 +54,6 @@ class ExpressionTokenizer private static final char DATE_LIT_QUOTE_CHAR = '#'; private static final char EQUALS_CHAR = '='; - // access times are based on this date (not the UTC base) - static final int BASE_DATE_YEAR = 1899; - static final int BASE_DATE_MONTH = 12; - static final int BASE_DATE_DAY = 30; - private static final String BASE_DATE_PREFIX = "1899/12/30 "; - private static final String BASE_DATE_FMT_PREFIX = "yyyy/M/d "; - - private static final String IMPLICIT_YEAR_FMT_PREFIX = "yyyy "; - private static final byte IS_OP_FLAG = 0x01; private static final byte IS_COMP_FLAG = 0x02; private static final byte IS_DELIM_FLAG = 0x04; @@ -305,14 +297,26 @@ class ExpressionTokenizer // note that although we may parse in the time "24" format, we will // display as the default time format - DateFormat parseDf = buf.getParseDateTimeFormat(type); + DateTimeFormatter parseDf = buf.getParseDateTimeFormat(type); try { - return new Token(TokenType.LITERAL, parseComplete(parseDf, dateStr), + TemporalAccessor parsedInfo = parseDf.parse(dateStr); + + LocalDate ld = ColumnImpl.BASE_LD; + if(type.includesDate()) { + ld = LocalDate.from(parsedInfo); + } + + LocalTime lt = ColumnImpl.BASE_LT; + if(type.includesTime()) { + lt = LocalTime.from(parsedInfo); + } + + return new Token(TokenType.LITERAL, LocalDateTime.of(ld, lt), dateStr, type.getValueType()); - } catch(java.text.ParseException pe) { + } catch(DateTimeException de) { throw new ParseException( - "Invalid date/time literal " + dateStr + " " + buf, pe); + "Invalid date/time literal " + dateStr + " " + buf, de); } } @@ -325,7 +329,7 @@ class ExpressionTokenizer boolean hasAmPm = false; if(hasTime) { - String[] amPmStrs = cfg.getDateFormatSymbols().getAmPmStrings(); + String[] amPmStrs = cfg.getAmPmStrings(); String amStr = " " + amPmStrs[0]; String pmStr = " " + amPmStrs[1]; hasAmPm = (hasSuffix(dateStr, amStr) || hasSuffix(dateStr, pmStr)); @@ -352,23 +356,6 @@ class ExpressionTokenizer suffStr, 0, suffStrLen)); } - static DateFormat createParseDateTimeFormat(TemporalConfig.Type type, - LocaleContext ctx) - { - if(type.isTimeOnly()) { - return new ParseTimeFormat(type, ctx); - } - - TemporalConfig cfg = ctx.getTemporalConfig(); - return ctx.createDateFormat(cfg.getDateTimeFormat(type)); - } - - static DateFormat createParseImplicitYearDateTimeFormat( - TemporalConfig.Type type, LocaleContext ctx) - { - return new ParseImplicitYearFormat(type, ctx); - } - private static Token maybeParseNumberLiteral(char firstChar, ExprBuf buf) { StringBuilder sb = buf.getScratchBuffer().append(firstChar); boolean hasDigit = isDigit(firstChar); @@ -464,29 +451,14 @@ class ExpressionTokenizer return new AbstractMap.SimpleImmutableEntry<K,V>(a, b); } - static Date parseComplete(DateFormat df, String str) - throws java.text.ParseException - { - // the java parsers will parse "successfully" even if there is leftover - // information. we only want to consider a parse operation successful if - // it parses the entire string (ignoring surrounding whitespace) - str = str.trim(); - ParsePosition pp = new ParsePosition(0); - Object d = df.parse(str, pp); - if(pp.getIndex() < str.length()) { - throw new java.text.ParseException("Failed parsing '" + str + "'", - pp.getIndex()); - } - return (Date)d; - } - private static final class ExprBuf { private final String _str; private final ParseContext _ctx; private int _pos; - private final Map<TemporalConfig.Type,DateFormat> _dateTimeFmts = - new EnumMap<TemporalConfig.Type,DateFormat>(TemporalConfig.Type.class); + private final Map<TemporalConfig.Type,DateTimeFormatter> _dateTimeFmts = + new EnumMap<TemporalConfig.Type,DateTimeFormatter>( + TemporalConfig.Type.class); private final StringBuilder _scratch = new StringBuilder(); private ExprBuf(String str, ParseContext ctx) { @@ -538,10 +510,11 @@ class ExpressionTokenizer return _ctx; } - public DateFormat getParseDateTimeFormat(TemporalConfig.Type type) { - DateFormat df = _dateTimeFmts.get(type); + public DateTimeFormatter getParseDateTimeFormat(TemporalConfig.Type type) { + DateTimeFormatter df = _dateTimeFmts.get(type); if(df == null) { - df = createParseDateTimeFormat(type, _ctx); + df = _ctx.createDateFormatter( + _ctx.getTemporalConfig().getDateTimeFormat(type)); _dateTimeFmts.put(type, df); } return df; @@ -605,98 +578,4 @@ class ExpressionTokenizer } } - /** - * Base DateFormat implementation for parsing date/time formats where - * additional information is added on to the format in order for it to be - * parsed correctly. - */ - private static abstract class ParsePrefixFormat extends DateFormat - { - private static final long serialVersionUID = 0L; - - private final DateFormat _parseDelegate; - - private ParsePrefixFormat(String formatPrefix, String formatStr, - LocaleContext ctx) { - _parseDelegate = ctx.createDateFormat(formatPrefix + formatStr); - } - - @Override - public StringBuffer format(Date date, StringBuffer toAppendTo, - FieldPosition fieldPosition) { - throw new UnsupportedOperationException(); - } - - @Override - public Date parse(String source, ParsePosition pos) { - String prefix = getPrefix(); - - Date result = _parseDelegate.parse(prefix + source, pos); - - // adjust index for original string - pos.setIndex(pos.getIndex() - prefix.length()); - - return result; - } - - @Override - public Calendar getCalendar() { - return _parseDelegate.getCalendar(); - } - - @Override - public TimeZone getTimeZone() { - return _parseDelegate.getTimeZone(); - } - - protected abstract String getPrefix(); - } - - /** - * Special date/time format which will parse time-only strings "correctly" - * according to how access handles time-only values. - */ - private static final class ParseTimeFormat extends ParsePrefixFormat - { - private static final long serialVersionUID = 0L; - - private ParseTimeFormat(TemporalConfig.Type timeType, LocaleContext ctx) { - super(BASE_DATE_FMT_PREFIX, - ctx.getTemporalConfig().getDateTimeFormat(timeType), ctx); - } - - @Override - protected String getPrefix() { - // we parse as a full date/time in order to get the correct "base date" - // used by access - return BASE_DATE_PREFIX; - } - } - - /** - * Special date/time format which will parse dates with implicit (current) - * years. - */ - private static final class ParseImplicitYearFormat extends ParsePrefixFormat - { - private static final long serialVersionUID = 0L; - - private ParseImplicitYearFormat(TemporalConfig.Type type, - LocaleContext ctx) { - super(IMPLICIT_YEAR_FMT_PREFIX, - ctx.getTemporalConfig().getImplicitYearDateTimeFormat(type), - ctx); - } - - @Override - protected String getPrefix() { - // need to get the current year - Calendar cal = getCalendar(); - cal.setTimeInMillis(System.currentTimeMillis()); - int year = cal.get(Calendar.YEAR); - // return a value matching IMPLICIT_YEAR_FMT_PREFIX - return year + " "; - } - } - } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java index d2bb847..92da3f6 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java @@ -17,13 +17,11 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; -import java.text.DateFormat; -import java.text.SimpleDateFormat; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; @@ -43,11 +41,10 @@ import com.healthmarketscience.jackcess.expr.FunctionLookup; import com.healthmarketscience.jackcess.expr.Identifier; import com.healthmarketscience.jackcess.expr.LocaleContext; import com.healthmarketscience.jackcess.expr.ParseException; -import com.healthmarketscience.jackcess.expr.TemporalConfig; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.Token; import com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.TokenType; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; /** @@ -68,8 +65,6 @@ public class Expressionator } public interface ParseContext extends LocaleContext { - public TemporalConfig getTemporalConfig(); - public SimpleDateFormat createDateFormat(String formatStr); public FunctionLookup getFunctionLookup(); } @@ -1318,7 +1313,7 @@ public class Expressionator case DATE: case TIME: case DATE_TIME: - return ValueSupport.toValue(valType, (Date)value); + return ValueSupport.toValue(valType, (LocalDateTime)value); case LONG: return ValueSupport.toValue((Integer)value); case DOUBLE: @@ -2081,7 +2076,7 @@ public class Expressionator case DATE: case TIME: case DATE_TIME: - return val.getAsDateTime(ctx); + return val.getAsLocalDateTime(ctx); case LONG: return val.getAsLongInt(ctx); case DOUBLE: diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java index 2c475eb..a21cd88 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java @@ -17,9 +17,9 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; -import java.text.DateFormat; import java.text.DecimalFormat; import java.text.NumberFormat; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; @@ -172,10 +172,10 @@ public class FormatUtil @Override public Value format(EvalContext ctx, Value expr, String fmtStr, - int firstDay, int firstWeekType) { - DateFormat sdf = ctx.createDateFormat( + int firstDay, int firstWeekType) { + DateTimeFormatter dtf = ctx.createDateFormatter( ctx.getTemporalConfig().getDateTimeFormat(_type)); - return ValueSupport.toValue(sdf.format(expr.getAsDateTime(ctx))); + return ValueSupport.toValue(dtf.format(expr.getAsLocalDateTime(ctx))); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java index 19b22eb..ca74f35 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java @@ -17,13 +17,12 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; -import java.math.BigInteger; import java.text.DecimalFormatSymbols; import com.healthmarketscience.jackcess.expr.EvalException; import com.healthmarketscience.jackcess.expr.LocaleContext; import com.healthmarketscience.jackcess.expr.Value; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; /** * @@ -84,7 +83,7 @@ public class StringValue extends BaseValue // numberToDateValue may return null for out of range numbers) try { dateValue = DefaultDateFunctions.numberToDateValue( - ctx, getNumber(ctx).doubleValue()); + getNumber(ctx).doubleValue()); } catch(EvalException ignored) { // not a number, not a date/time } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java index 3040920..805cc32 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java @@ -18,9 +18,10 @@ package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; import java.math.BigInteger; -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; import java.util.regex.Pattern; import com.healthmarketscience.jackcess.expr.EvalException; @@ -99,36 +100,42 @@ public class ValueSupport return new BigDecimalValue(normalize(s)); } - public static Value toDateValue(LocaleContext ctx, Value.Type type, double dd) - { - return toValue(type, new Date( - ColumnImpl.fromDateDouble(dd, ctx.getCalendar()))); + static Value toDateValueIfPossible(Value.Type dateType, double dd) { + if(DefaultDateFunctions.isValidDateDouble(dd)) { + return ValueSupport.toValue( + dateType, ColumnImpl.ldtFromLocalDateDouble(dd)); + } + return ValueSupport.toValue(dd); + } + + public static Value toValue(LocalDate ld) { + return new DateTimeValue( + Value.Type.DATE, LocalDateTime.of(ld, ColumnImpl.BASE_LT)); } - public static Value toValue(Calendar cal) { - return new DateTimeValue(getDateTimeType(cal), cal.getTime()); + public static Value toValue(LocalTime lt) { + return new DateTimeValue( + Value.Type.TIME, LocalDateTime.of(ColumnImpl.BASE_LD, lt)); } - public static Value.Type getDateTimeType(Calendar cal) { - boolean hasTime = ((cal.get(Calendar.HOUR_OF_DAY) != 0) || - (cal.get(Calendar.MINUTE) != 0) || - (cal.get(Calendar.SECOND) != 0)); + public static Value toValue(LocalDateTime ldt) { + return new DateTimeValue(getDateTimeType(ldt), ldt); + } - boolean hasDate = - ((cal.get(Calendar.YEAR) != ExpressionTokenizer.BASE_DATE_YEAR) || - ((cal.get(Calendar.MONTH) + 1) != ExpressionTokenizer.BASE_DATE_MONTH) || - (cal.get(Calendar.DAY_OF_MONTH) != ExpressionTokenizer.BASE_DATE_DAY)); + public static Value.Type getDateTimeType(LocalDateTime ldt) { + boolean hasDate = !ColumnImpl.BASE_LD.equals(ldt.toLocalDate()); + boolean hasTime = !ColumnImpl.BASE_LT.equals(ldt.toLocalTime()); return (hasDate ? (hasTime ? Value.Type.DATE_TIME : Value.Type.DATE) : Value.Type.TIME); } - public static Value toValue(Value.Type type, Date d) { - return new DateTimeValue(type, d); + public static Value toValue(Value.Type type, LocalDateTime ldt) { + return new DateTimeValue(type, ldt); } - public static DateFormat getDateFormatForType(LocaleContext ctx, Value.Type type) { + public static DateTimeFormatter getDateFormatForType(LocaleContext ctx, Value.Type type) { String fmtStr = null; switch(type) { case DATE: @@ -143,7 +150,7 @@ public class ValueSupport default: throw new EvalException("Unexpected date/time type " + type); } - return ctx.createDateFormat(fmtStr); + return ctx.createDateFormatter(fmtStr); } /** diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/query/QueryFormat.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/QueryFormat.java index f9513d4..f0272f3 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/query/QueryFormat.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/QueryFormat.java @@ -22,14 +22,13 @@ import java.util.regex.Pattern; import com.healthmarketscience.jackcess.DataType; import com.healthmarketscience.jackcess.query.Query; -import org.apache.commons.lang.SystemUtils; /** * Constants used by the query data parsing. - * + * * @author James Ahlborn */ -public class QueryFormat +public class QueryFormat { private QueryFormat() {} @@ -90,7 +89,7 @@ public class QueryFormat public static final short APPEND_VALUE_FLAG = (short)0x8000; public static final short CROSSTAB_PIVOT_FLAG = 0x01; - public static final short CROSSTAB_NORMAL_FLAG = 0x02; + public static final short CROSSTAB_NORMAL_FLAG = 0x02; public static final String UNION_PART1 = "X7YZ_____1"; public static final String UNION_PART2 = "X7YZ_____2"; @@ -102,10 +101,10 @@ public class QueryFormat public static final Pattern IDENTIFIER_SEP_PAT = Pattern.compile("\\."); public static final char IDENTIFIER_SEP_CHAR = '.'; - public static final String NEWLINE = SystemUtils.LINE_SEPARATOR; + public static final String NEWLINE = System.lineSeparator(); - public static final Map<Short,String> PARAM_TYPE_MAP = + public static final Map<Short,String> PARAM_TYPE_MAP = new HashMap<Short,String>(); static { PARAM_TYPE_MAP.put((short)0, "Value"); @@ -123,7 +122,7 @@ public class QueryFormat PARAM_TYPE_MAP.put((short)DataType.GUID.getValue(), "Guid"); } - public static final Map<Short,String> JOIN_TYPE_MAP = + public static final Map<Short,String> JOIN_TYPE_MAP = new HashMap<Short,String>(); static { JOIN_TYPE_MAP.put((short)1, " INNER JOIN "); @@ -131,7 +130,7 @@ public class QueryFormat JOIN_TYPE_MAP.put((short)3, " RIGHT JOIN "); } - public static final Map<Short,Query.Type> TYPE_MAP = + public static final Map<Short,Query.Type> TYPE_MAP = new HashMap<Short,Query.Type>(); static { for(Query.Type type : Query.Type.values()) { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/query/QueryImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/QueryImpl.java index 6b51236..389ca90 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/query/QueryImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/QueryImpl.java @@ -27,7 +27,7 @@ import com.healthmarketscience.jackcess.impl.RowIdImpl; import com.healthmarketscience.jackcess.impl.RowImpl; import static com.healthmarketscience.jackcess.impl.query.QueryFormat.*; import com.healthmarketscience.jackcess.query.Query; -import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java b/src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java index fce5289..1dc0b80 100644 --- a/src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java +++ b/src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java @@ -17,10 +17,11 @@ limitations under the License. package com.healthmarketscience.jackcess.util; import java.io.Closeable; -import java.io.File; import java.io.IOException; -import java.io.RandomAccessFile; import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Random; import com.healthmarketscience.jackcess.Database; @@ -64,11 +65,11 @@ public abstract class CustomLinkResolver implements LinkResolver /** temp dbs default to the filesystem, not in memory */ public static final boolean DEFAULT_IN_MEMORY = false; /** temp dbs end up in the system temp dir by default */ - public static final File DEFAULT_TEMP_DIR = null; + public static final Path DEFAULT_TEMP_DIR = null; private final FileFormat _defaultFormat; private final boolean _defaultInMemory; - private final File _defaultTempDir; + private final Path _defaultTempDir; /** * Creates a CustomLinkResolver using the default behavior for creating temp @@ -93,7 +94,7 @@ public abstract class CustomLinkResolver implements LinkResolver * directory) */ protected CustomLinkResolver(FileFormat defaultFormat, boolean defaultInMemory, - File defaultTempDir) + Path defaultTempDir) { _defaultFormat = defaultFormat; _defaultInMemory = defaultInMemory; @@ -108,7 +109,7 @@ public abstract class CustomLinkResolver implements LinkResolver return _defaultInMemory; } - protected File getDefaultTempDirectory() { + protected Path getDefaultTempDirectory() { return _defaultTempDir; } @@ -161,24 +162,26 @@ public abstract class CustomLinkResolver implements LinkResolver * @return the temp db for holding the linked table info */ protected Database createTempDb(Object customFile, FileFormat format, - boolean inMemory, File tempDir, + boolean inMemory, Path tempDir, boolean readOnly) throws IOException { - File dbFile = null; + Path dbFile = null; FileChannel channel = null; boolean success = false; try { if(inMemory) { - dbFile = new File(MEM_DB_PREFIX + DB_ID.nextLong() + - format.getFileExtension()); + dbFile = Paths.get(MEM_DB_PREFIX + DB_ID.nextLong() + + format.getFileExtension()); channel = MemFileChannel.newChannel(); } else { - dbFile = File.createTempFile(FILE_DB_PREFIX, format.getFileExtension(), - tempDir); - channel = new RandomAccessFile(dbFile, DatabaseImpl.RW_CHANNEL_MODE) - .getChannel(); + dbFile = ((tempDir != null) ? + Files.createTempFile(tempDir, FILE_DB_PREFIX, + format.getFileExtension()) : + Files.createTempFile(FILE_DB_PREFIX, + format.getFileExtension())); + channel = FileChannel.open(dbFile, DatabaseImpl.RW_CHANNEL_OPTS); } TempDatabaseImpl.initDbChannel(channel, format); @@ -196,9 +199,12 @@ public abstract class CustomLinkResolver implements LinkResolver } } - private static void deleteDbFile(File dbFile) { - if((dbFile != null) && (dbFile.getName().startsWith(FILE_DB_PREFIX))) { - dbFile.delete(); + private static void deleteDbFile(Path dbFile) { + if((dbFile != null) && + dbFile.getFileName().toString().startsWith(FILE_DB_PREFIX)) { + try { + Files.deleteIfExists(dbFile); + } catch(IOException ignores) {} } } @@ -256,7 +262,7 @@ public abstract class CustomLinkResolver implements LinkResolver private final Object _customFile; protected TempDatabaseImpl(CustomLinkResolver resolver, Object customFile, - File file, FileChannel channel, + Path file, FileChannel channel, FileFormat fileFormat, boolean readOnly) throws IOException { @@ -283,7 +289,7 @@ public abstract class CustomLinkResolver implements LinkResolver try { super.close(); } finally { - deleteDbFile(getFile()); + deleteDbFile(getPath()); closeCustomFile(_customFile); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java b/src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java index 528ddbb..4420938 100644 --- a/src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java +++ b/src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java @@ -29,6 +29,9 @@ import java.nio.channels.FileLock; import java.nio.channels.NonWritableChannelException; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import com.healthmarketscience.jackcess.Database; import com.healthmarketscience.jackcess.DatabaseBuilder; @@ -41,11 +44,11 @@ import com.healthmarketscience.jackcess.impl.DatabaseImpl; * where disk usage may not be possible or desirable). Obviously, this * requires enough jvm heap space to fit the file data. Use one of the * {@code newChannel()} methods to construct an instance of this class. - * <p/> + * <br> * In order to use this class with a Database, you <i>must</i> use the {@link * DatabaseBuilder} to open/create the Database instance, passing an instance * of this class to the {@link DatabaseBuilder#setChannel} method. - * <p/> + * <br> * Implementation note: this class is optimized for use with {@link Database}. * Therefore not all methods may be implemented and individual read/write * operations are only supported within page boundaries. @@ -53,8 +56,13 @@ import com.healthmarketscience.jackcess.impl.DatabaseImpl; * @author James Ahlborn * @usage _advanced_class_ */ -public class MemFileChannel extends FileChannel +public class MemFileChannel extends FileChannel { + /** read-only channel access mode */ + public static final String RO_CHANNEL_MODE = "r"; + /** read/write channel access mode */ + public static final String RW_CHANNEL_MODE = "rw"; + private static final byte[][] EMPTY_DATA = new byte[0][]; // use largest possible Jet "page size" to ensure that reads/writes will @@ -68,10 +76,10 @@ public class MemFileChannel extends FileChannel /** current amount of actual data in the file */ private long _size; /** chunks containing the file data. the length of the chunk array is - always a power of 2 and the chunks are always CHUNK_SIZE. */ + always a power of 2 and the chunks are always CHUNK_SIZE. */ private byte[][] _data; - private MemFileChannel() + private MemFileChannel() { this(0L, 0L, EMPTY_DATA); } @@ -95,7 +103,7 @@ public class MemFileChannel extends FileChannel * affect the original File source. */ public static MemFileChannel newChannel(File file) throws IOException { - return newChannel(file, DatabaseImpl.RW_CHANNEL_MODE); + return newChannel(file, RW_CHANNEL_MODE); } /** @@ -105,13 +113,41 @@ public class MemFileChannel extends FileChannel * modifications to the returned channel will <i>not</i> affect the original * File source. */ - public static MemFileChannel newChannel(File file, String mode) - throws IOException + public static MemFileChannel newChannel(File file, String mode) + throws IOException { FileChannel in = null; try { return newChannel(in = new RandomAccessFile( - file, DatabaseImpl.RO_CHANNEL_MODE).getChannel(), + file, RO_CHANNEL_MODE).getChannel(), + mode); + } finally { + ByteUtil.closeQuietly(in); + } + } + + /** + * Creates a new MemFileChannel containing the contents of the + * given Path with the given mode (for mode details see + * {@link RandomAccessFile#RandomAccessFile(File,String)}). Note, + * modifications to the returned channel will <i>not</i> affect the original + * File source. + */ + public static MemFileChannel newChannel(Path file, OpenOption... opts) + throws IOException + { + FileChannel in = null; + try { + String mode = RO_CHANNEL_MODE; + if(opts != null) { + for(OpenOption opt : opts) { + if(opt == StandardOpenOption.WRITE) { + mode = RW_CHANNEL_MODE; + break; + } + } + } + return newChannel(in = FileChannel.open(file, StandardOpenOption.READ), mode); } finally { ByteUtil.closeQuietly(in); @@ -120,10 +156,19 @@ public class MemFileChannel extends FileChannel /** * Creates a new read/write MemFileChannel containing the contents of the + * given Path. Note, modifications to the returned channel will <i>not</i> + * affect the original File source. + */ + public static MemFileChannel newChannel(Path file) throws IOException { + return newChannel(file, DatabaseImpl.RW_CHANNEL_OPTS); + } + + /** + * Creates a new read/write MemFileChannel containing the contents of the * given InputStream. */ public static MemFileChannel newChannel(InputStream in) throws IOException { - return newChannel(in, DatabaseImpl.RW_CHANNEL_MODE); + return newChannel(in, RW_CHANNEL_MODE); } /** @@ -131,8 +176,8 @@ public class MemFileChannel extends FileChannel * given InputStream with the given mode (for mode details see * {@link RandomAccessFile#RandomAccessFile(File,String)}). */ - public static MemFileChannel newChannel(InputStream in, String mode) - throws IOException + public static MemFileChannel newChannel(InputStream in, String mode) + throws IOException { return newChannel(Channels.newChannel(in), mode); } @@ -141,10 +186,10 @@ public class MemFileChannel extends FileChannel * Creates a new read/write MemFileChannel containing the contents of the * given ReadableByteChannel. */ - public static MemFileChannel newChannel(ReadableByteChannel in) + public static MemFileChannel newChannel(ReadableByteChannel in) throws IOException { - return newChannel(in, DatabaseImpl.RW_CHANNEL_MODE); + return newChannel(in, RW_CHANNEL_MODE); } /** @@ -152,7 +197,7 @@ public class MemFileChannel extends FileChannel * given ReadableByteChannel with the given mode (for mode details see * {@link RandomAccessFile#RandomAccessFile(File,String)}). */ - public static MemFileChannel newChannel(ReadableByteChannel in, String mode) + public static MemFileChannel newChannel(ReadableByteChannel in, String mode) throws IOException { MemFileChannel channel = new MemFileChannel(); @@ -282,7 +327,7 @@ public class MemFileChannel extends FileChannel if(position >= _size) { return 0L; } - + count = Math.min(count, _size - position); int chunkIndex = getChunkIndex(position); @@ -304,7 +349,7 @@ public class MemFileChannel extends FileChannel numBytes += bytesWritten; count -= bytesWritten; } while(src.hasRemaining()); - + ++chunkIndex; chunkOffset = 0; } @@ -360,11 +405,11 @@ public class MemFileChannel extends FileChannel count -= bytesRead; _size = Math.max(_size, position + numBytes); } while(dst.hasRemaining()); - + ++chunkIndex; - chunkOffset = 0; + chunkOffset = 0; } - + return numBytes; } @@ -410,7 +455,7 @@ public class MemFileChannel extends FileChannel private static int getChunkIndex(long pos) { return (int)(pos / CHUNK_SIZE); } - + private static int getChunkOffset(long pos) { return (int)(pos % CHUNK_SIZE); } @@ -418,7 +463,7 @@ public class MemFileChannel extends FileChannel private static int getNumChunks(long size) { return getChunkIndex(size + CHUNK_SIZE - 1); } - + @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException @@ -433,7 +478,7 @@ public class MemFileChannel extends FileChannel @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException - { + { long numBytes = 0L; for(int i = offset; i < offset + length; ++i) { if(_position >= _size) { @@ -474,7 +519,7 @@ public class MemFileChannel extends FileChannel { super(channel._position, channel._size, channel._data); } - + @Override public int write(ByteBuffer src, long position) throws IOException { throw new NonWritableChannelException(); @@ -491,6 +536,6 @@ public class MemFileChannel extends FileChannel throws IOException { throw new NonWritableChannelException(); - } + } } } diff --git a/src/main/java/com/healthmarketscience/jackcess/util/OleBlob.java b/src/main/java/com/healthmarketscience/jackcess/util/OleBlob.java index 8a775ad..4a85e13 100644 --- a/src/main/java/com/healthmarketscience/jackcess/util/OleBlob.java +++ b/src/main/java/com/healthmarketscience/jackcess/util/OleBlob.java @@ -34,7 +34,7 @@ import com.healthmarketscience.jackcess.impl.OleUtil; * with nested filesystems!), and jackcess only supports a small portion of * it. That said, jackcess should support the bulk of the common * functionality. - * <p/> + * <br> * The main Blob methods will interact with the <i>entire</i> OLE field data * which, in most cases, contains additional wrapper information. In order to * access the ultimate "content" contained within the OLE data, the {@link @@ -42,15 +42,15 @@ import com.healthmarketscience.jackcess.impl.OleUtil; * variety of formats, so additional sub-interfaces are available to interact * with it. The most specific sub-interface can be determined by the {@link * ContentType} of the Content. - * <p/> + * <br> * Once an OleBlob is no longer useful, <i>it should be closed</i> using * {@link #free} or {@link #close} methods (after which, the instance will no * longer be functional). - * <p/> + * <br> * Note, the OleBlob implementation is read-only (through the interface). In * order to modify blob contents, create a new OleBlob instance using {@link * OleBlob.Builder} and write it to the access database. - * <p/> + * <br> * <b>Example for interpreting an existing OLE field:</b> * <pre> * OleBlob oleBlob = null; @@ -66,7 +66,7 @@ import com.healthmarketscience.jackcess.impl.OleUtil; * if(oleBlob != null) { oleBlob.close(); } * } * </pre> - * <p/> + * <br> * <b>Example for creating new, embedded ole data:</b> * <pre> * OleBlob oleBlob = null; @@ -79,7 +79,7 @@ import com.healthmarketscience.jackcess.impl.OleUtil; * if(oleBlob != null) { oleBlob.close(); } * } * </pre> - * <p/> + * <br> * <b>Example for creating new, linked ole data:</b> * <pre> * OleBlob oleBlob = null; diff --git a/src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java b/src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java index 85620e8..0fb6f50 100644 --- a/src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java +++ b/src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java @@ -28,7 +28,7 @@ import com.healthmarketscience.jackcess.Database; /** * Wrapper for existing FileChannel which is read-only. - * <p/> + * <br> * Implementation note: this class is optimized for use with {@link Database}. * Therefore not all methods may be implemented. * diff --git a/src/main/java/com/healthmarketscience/jackcess/util/RowFilter.java b/src/main/java/com/healthmarketscience/jackcess/util/RowFilter.java index 440d9dd..607bf9a 100644 --- a/src/main/java/com/healthmarketscience/jackcess/util/RowFilter.java +++ b/src/main/java/com/healthmarketscience/jackcess/util/RowFilter.java @@ -19,17 +19,17 @@ package com.healthmarketscience.jackcess.util; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import com.healthmarketscience.jackcess.Column; import com.healthmarketscience.jackcess.Row; -import org.apache.commons.lang.ObjectUtils; /** * The RowFilter class encapsulates a filter test for a table row. This can * be used by the {@link #apply(Iterable)} method to create an Iterable over a * table which returns only rows matching some criteria. - * + * * @author Patricia Donaldson, Xerox Corporation * @usage _general_class_ */ @@ -59,22 +59,22 @@ public abstract class RowFilter /** * Creates a filter based on a row pattern. - * + * * @param rowPattern Map from column names to the values to be matched. * A table row will match the target if - * {@code ObjectUtils.equals(rowPattern.get(s), row.get(s))} + * {@code Objects.equals(rowPattern.get(s), row.get(s))} * for all column names in the pattern map. * @return a filter which matches table rows which match the values in the * row pattern */ - public static RowFilter matchPattern(final Map<String,?> rowPattern) + public static RowFilter matchPattern(final Map<String,?> rowPattern) { return new RowFilter() { @Override - public boolean matches(Row row) + public boolean matches(Row row) { for(Map.Entry<String,?> e : rowPattern.entrySet()) { - if(!ObjectUtils.equals(e.getValue(), row.get(e.getKey()))) { + if(!Objects.equals(e.getValue(), row.get(e.getKey()))) { return false; } } @@ -89,18 +89,18 @@ public abstract class RowFilter * @param columnPattern column to be matched * @param valuePattern value to be matched. * A table row will match the target if - * {@code ObjectUtils.equals(valuePattern, row.get(columnPattern.getName()))}. + * {@code Objects.equals(valuePattern, row.get(columnPattern.getName()))}. * @return a filter which matches table rows which match the value in the * row pattern */ - public static RowFilter matchPattern(final Column columnPattern, - final Object valuePattern) + public static RowFilter matchPattern(final Column columnPattern, + final Object valuePattern) { return new RowFilter() { @Override - public boolean matches(Row row) + public boolean matches(Row row) { - return ObjectUtils.equals(valuePattern, columnPattern.getRowValue(row)); + return Objects.equals(valuePattern, columnPattern.getRowValue(row)); } }; } @@ -118,7 +118,7 @@ public abstract class RowFilter { return new RowFilter() { @Override - public boolean matches(Row row) + public boolean matches(Row row) { return !filter.matches(row); } @@ -140,7 +140,7 @@ public abstract class RowFilter public static Iterable<Row> apply(RowFilter rowFilter, Iterable<? extends Row> iterable) { - return((rowFilter != null) ? rowFilter.apply(iterable) : + return((rowFilter != null) ? rowFilter.apply(iterable) : (Iterable<Row>)iterable); } @@ -152,7 +152,7 @@ public abstract class RowFilter { private final Iterable<? extends Row> _iterable; - private FilterIterable(Iterable<? extends Row> iterable) + private FilterIterable(Iterable<? extends Row> iterable) { _iterable = iterable; } @@ -163,7 +163,7 @@ public abstract class RowFilter * iterable, returning only rows for which the {@link RowFilter#matches} * method returns {@code true} */ - public Iterator<Row> iterator() + public Iterator<Row> iterator() { return new Iterator<Row>() { private final Iterator<? extends Row> _iter = _iterable.iterator(); diff --git a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java index a564834..b814e93 100644 --- a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java +++ b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java @@ -19,12 +19,12 @@ package com.healthmarketscience.jackcess.util; import java.io.IOException; import java.util.Arrays; +import java.util.Objects; import com.healthmarketscience.jackcess.DataType; import com.healthmarketscience.jackcess.Table; import com.healthmarketscience.jackcess.impl.ColumnImpl; import com.healthmarketscience.jackcess.impl.DatabaseImpl; -import org.apache.commons.lang.ObjectUtils; /** * Simple concrete implementation of ColumnMatcher which tests for equality. @@ -48,7 +48,7 @@ public class SimpleColumnMatcher implements ColumnMatcher { return true; } - if((value1 != null) && (value2 != null) && + if((value1 != null) && (value2 != null) && (value1.getClass() != value2.getClass())) { // the values aren't the same type, try coercing them to "internal" @@ -58,7 +58,7 @@ public class SimpleColumnMatcher implements ColumnMatcher { DatabaseImpl db = (DatabaseImpl)table.getDatabase(); Object internalV1 = ColumnImpl.toInternalValue(dataType, value1, db); Object internalV2 = ColumnImpl.toInternalValue(dataType, value2, db); - + return equals(internalV1, internalV2); } catch(IOException e) { // ignored, just go with the original result @@ -73,7 +73,7 @@ public class SimpleColumnMatcher implements ColumnMatcher { */ private static boolean equals(Object o1, Object o2) { - return (ObjectUtils.equals(o1, o2) || + return (Objects.equals(o1, o2) || ((o1 instanceof byte[]) && (o2 instanceof byte[]) && Arrays.equals((byte[])o1, (byte[])o2))); } diff --git a/src/main/resources/com/healthmarketscience/jackcess/log4j.properties b/src/main/resources/com/healthmarketscience/jackcess/log4j.properties deleted file mode 100644 index 092468c..0000000 --- a/src/main/resources/com/healthmarketscience/jackcess/log4j.properties +++ /dev/null @@ -1,6 +0,0 @@ -log4j.rootCategory=INFO, stdout -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=**** %-5p %d{MMM d HH:mm:ss} [%F] - %m%n - -log4j.category.com.healthmarketscience.jackcess=INFO diff --git a/src/site/javadoc/taglets.properties b/src/site/javadoc/taglets.properties deleted file mode 100644 index b24a1e3..0000000 --- a/src/site/javadoc/taglets.properties +++ /dev/null @@ -1,41 +0,0 @@ -# basic taglets config -Taglets.splash=false -Taglets.verbose=false -Taglets.debug=false -Taglets.drivers= drivers/j2se15.jar, drivers/j2se14.jar - -# custom usage formatting -Taglets.shutdown.usage-tag= net.sourceforge.taglets.simple.shutdown.RegexReplacer -Taglets.shutdown.usage-tag.files= **/*.html -Taglets.shutdown.usage-tag.token.0=_general_method_ -Taglets.shutdown.usage-tag.value.0=<span class="UsageGeneral"><span class="UsageGeneralHeader">General</span>: This method is general use.</span> - -Taglets.shutdown.usage-tag.token.1=_intermediate_method_ -Taglets.shutdown.usage-tag.value.1=<span class="UsageIntermediate"><span class="UsageIntermediateHeader">Intermediate</span>: This method requires moderate API knowledge.</span> - -Taglets.shutdown.usage-tag.token.2=_advanced_method_ -Taglets.shutdown.usage-tag.value.2=<span class="UsageAdvanced"><span class="UsageAdvancedHeader">Advanced</span>: This method is for advanced/internal use.</span> - -Taglets.shutdown.usage-tag.token.3=_general_class_ -Taglets.shutdown.usage-tag.value.3=<span class="UsageGeneral"><span class="UsageGeneralHeader">General</span>: This class is general use.</span> - -Taglets.shutdown.usage-tag.token.4=_intermediate_class_ -Taglets.shutdown.usage-tag.value.4=<span class="UsageIntermediate"><span class="UsageIntermediateHeader">Intermediate</span>: This class requires moderate API knowledge.</span> - -Taglets.shutdown.usage-tag.token.5=_advanced_class_ -Taglets.shutdown.usage-tag.value.5=<span class="UsageAdvanced"><span class="UsageAdvancedHeader">Advanced</span>: This class is for advanced/internal use.</span> - -Taglets.shutdown.usage-tag.token.6=_general_field_ -Taglets.shutdown.usage-tag.value.6=<span class="UsageGeneral"><span class="UsageGeneralHeader">General</span>: This field is general use.</span> - -Taglets.shutdown.usage-tag.token.7=_intermediate_field_ -Taglets.shutdown.usage-tag.value.7=<span class="UsageIntermediate"><span class="UsageIntermediateHeader">Intermediate</span>: This field requires moderate API knowledge.</span> - -Taglets.shutdown.usage-tag.token.8=_advanced_field_ -Taglets.shutdown.usage-tag.value.8=<span class="UsageAdvanced"><span class="UsageAdvancedHeader">Advanced</span>: This field is for advanced/internal use.</span> - - -# apparently we need one "normal" tag or the taglets code gets unhappy -Taglets.taglet.todo= net.sourceforge.taglets.simple.block.ParamBlockTaglet -Taglets.taglet.todo.dl.class= tagletsTodo -Taglets.taglet.todo.dl.header= <b>Todo:</b> diff --git a/src/test/java/com/healthmarketscience/jackcess/ComplexColumnTest.java b/src/test/java/com/healthmarketscience/jackcess/ComplexColumnTest.java index b4704bf..c8b81a3 100644 --- a/src/test/java/com/healthmarketscience/jackcess/ComplexColumnTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/ComplexColumnTest.java @@ -39,7 +39,8 @@ import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; * * @author James Ahlborn */ -public class ComplexColumnTest extends TestCase +@SuppressWarnings("deprecation") +public class ComplexColumnTest extends TestCase { public ComplexColumnTest(String name) { @@ -66,7 +67,7 @@ public class ComplexColumnTest extends TestCase (ComplexValueForeignKey)verCol.getRowValue(row); String curValue = (String)col.getRowValue(row); - + if(rowId.equals("row1")) { checkVersions(1, complexValueFk, curValue); } else if(rowId.equals("row2")) { @@ -94,7 +95,7 @@ public class ComplexColumnTest extends TestCase Date upTime = new Date(); row8ValFk.addVersion("row8-memo", upTime); checkVersions(row8ValFk.get(), row8ValFk, "row8-memo", - "row8-memo", upTime); + "row8-memo", upTime); Cursor cursor = CursorBuilder.createCursor(t1); assertTrue(cursor.findFirstRow(t1.getColumn("id"), "row3")); @@ -120,7 +121,7 @@ public class ComplexColumnTest extends TestCase "row3-memo-again", new Date(1315876965382L), "row3-memo-revised", new Date(1315876953077L), "row3-memo", new Date(1315876879126L)); - + try { v.delete(); fail("UnsupportedOperationException should have been thrown"); @@ -133,7 +134,7 @@ public class ComplexColumnTest extends TestCase "row3-memo-again", new Date(1315876965382L), "row3-memo-revised", new Date(1315876953077L), "row3-memo", new Date(1315876879126L)); - + try { v.getComplexValueForeignKey().deleteAllValues(); fail("UnsupportedOperationException should have been thrown"); @@ -146,7 +147,7 @@ public class ComplexColumnTest extends TestCase "row3-memo-again", new Date(1315876965382L), "row3-memo-revised", new Date(1315876953077L), "row3-memo", new Date(1315876879126L)); - + db.close(); } } @@ -154,7 +155,7 @@ public class ComplexColumnTest extends TestCase public void testAttachments() throws Exception { for(final TestDB testDB : TestDB.getSupportedForBasename(Basename.COMPLEX)) { - + Database db = openCopy(testDB); Table t1 = db.getTable("Table1"); @@ -187,12 +188,12 @@ public class ComplexColumnTest extends TestCase ComplexValueForeignKey row8ValFk = (ComplexValueForeignKey) col.getRowValue(row8); row8ValFk.addAttachment(null, "test_data.txt", "txt", - getFileBytes("test_data.txt"), null, null); + getFileBytes("test_data.txt"), (Date)null, null); checkAttachments(row8ValFk.get(), row8ValFk, "test_data.txt"); row8ValFk.addEncodedAttachment(null, "test_data2.txt", "txt", - getEncodedFileBytes("test_data2.txt"), null, - null); - checkAttachments(row8ValFk.get(), row8ValFk, "test_data.txt", + getEncodedFileBytes("test_data2.txt"), + (Date)null, null); + checkAttachments(row8ValFk.get(), row8ValFk, "test_data.txt", "test_data2.txt"); Cursor cursor = CursorBuilder.createCursor(t1); @@ -200,8 +201,8 @@ public class ComplexColumnTest extends TestCase ComplexValueForeignKey row4ValFk = (ComplexValueForeignKey) cursor.getCurrentRowValue(col); Attachment a = row4ValFk.addAttachment(null, "test_data.txt", "txt", - getFileBytes("test_data.txt"), null, - null); + getFileBytes("test_data.txt"), + (Date)null, null); checkAttachments(4, row4ValFk, "test_data2.txt", "test_data.txt"); a.setFileType("zip"); @@ -230,8 +231,8 @@ public class ComplexColumnTest extends TestCase ComplexValueForeignKey row2ValFk = (ComplexValueForeignKey) cursor.getCurrentRowValue(col); row2ValFk.deleteAllValues(); - checkAttachments(2, row2ValFk); - + checkAttachments(2, row2ValFk); + db.close(); } } @@ -239,7 +240,7 @@ public class ComplexColumnTest extends TestCase public void testMultiValues() throws Exception { for(final TestDB testDB : TestDB.getSupportedForBasename(Basename.COMPLEX)) { - + Database db = openCopy(testDB); Table t1 = db.getTable("Table1"); @@ -264,7 +265,7 @@ public class ComplexColumnTest extends TestCase } else { assertTrue(false); } - } + } Object[] row8 = {"row8", Column.AUTO_NUMBER, "some-data", "row8-memo", Column.AUTO_NUMBER, Column.AUTO_NUMBER}; @@ -307,17 +308,17 @@ public class ComplexColumnTest extends TestCase PropertyMap props = col.getProperties(); assertEquals(Boolean.TRUE, props.getValue(PropertyMap.ALLOW_MULTI_VALUE_PROP)); assertEquals("Value List", props.getValue(PropertyMap.ROW_SOURCE_TYPE_PROP)); - assertEquals("\"value1\";\"value2\";\"value3\";\"value4\"", + assertEquals("\"value1\";\"value2\";\"value3\";\"value4\"", props.getValue(PropertyMap.ROW_SOURCE_PROP)); - + db.close(); } } - + public void testUnsupported() throws Exception { for(final TestDB testDB : TestDB.getSupportedForBasename(Basename.UNSUPPORTED)) { - + Database db = openCopy(testDB); Table t1 = db.getTable("Test"); @@ -331,7 +332,7 @@ public class ComplexColumnTest extends TestCase (ComplexValueForeignKey)col.getRowValue(row); if(rowId.equals(1)) { - checkUnsupportedValues(1, complexValueFk, + checkUnsupportedValues(1, complexValueFk, "RawData[(5) FF FE 62 61 7A]"); } else if(rowId.equals(2)) { checkUnsupportedValues(2, complexValueFk, "RawData[(5) FF FE 66 6F 6F]", "RawData[(5) FF FE 62 61 7A]"); @@ -340,12 +341,12 @@ public class ComplexColumnTest extends TestCase } else { assertTrue(false); } - } - + } + db.close(); } } - + private static void checkVersions( int cValId, ComplexValueForeignKey complexValueFk, String curValue, Object... versionInfos) @@ -376,7 +377,7 @@ public class ComplexColumnTest extends TestCase throws Exception { assertEquals(cValId, complexValueFk.get()); - + List<Attachment> attachments = complexValueFk.getAttachments(); if(fileNames.length == 0) { assertTrue(attachments.isEmpty()); @@ -388,12 +389,12 @@ public class ComplexColumnTest extends TestCase assertEquals(fname, a.getFileName()); assertEquals("txt", a.getFileType()); assertTrue(Arrays.equals(getFileBytes(fname), a.getFileData())); - assertTrue(Arrays.equals(getEncodedFileBytes(fname), + assertTrue(Arrays.equals(getEncodedFileBytes(fname), a.getEncodedFileData())); } } } - + private static void checkMultiValues( int cValId, ComplexValueForeignKey complexValueFk, Object... expectedValues) @@ -411,7 +412,7 @@ public class ComplexColumnTest extends TestCase SingleValue v = values.get(i); assertEquals(value, v.get()); } - } + } } private static void checkUnsupportedValues( @@ -434,7 +435,7 @@ public class ComplexColumnTest extends TestCase assertTrue(ColumnImpl.isRawData(rv)); assertEquals(value, rv.toString()); } - } + } } private static byte[] getFileBytes(String fname) throws Exception @@ -447,7 +448,7 @@ public class ComplexColumnTest extends TestCase } throw new RuntimeException("unexpected bytes"); } - + private static byte[] getEncodedFileBytes(String fname) throws Exception { if("test_data.txt".equals(fname)) { @@ -458,9 +459,9 @@ public class ComplexColumnTest extends TestCase } throw new RuntimeException("unexpected bytes"); } - + private static byte b(int i) { return (byte)i; } - + private static byte[] getAsciiBytes(String str) { try { return str.getBytes("US-ASCII"); @@ -468,7 +469,7 @@ public class ComplexColumnTest extends TestCase throw new RuntimeException(e); } } - + private static final byte[] TEST_ENC_BYTES = new byte[] { b(0x01),b(0x00),b(0x00),b(0x00),b(0x3A),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62), b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89),b(0x25),b(0x89),b(0x0A),b(0x69),b(0xF9), @@ -476,7 +477,7 @@ public class ComplexColumnTest extends TestCase }; private static final byte[] TEST_BYTES = getAsciiBytes("this is some test data for attachment."); - + private static final byte[] TEST2_ENC_BYTES = new byte[] { b(0x01),b(0x00),b(0x00),b(0x00),b(0x3F),b(0x00),b(0x00),b(0x00),b(0x78),b(0x5E),b(0x13),b(0x61),b(0x60),b(0x60),b(0x60),b(0x04),b(0x62),b(0x16),b(0x20),b(0x2E),b(0x61),b(0xA8),b(0x00),b(0x62), b(0x20),b(0x9D),b(0x91),b(0x59),b(0xAC),b(0x00),b(0x44),b(0xC5),b(0xF9),b(0xB9),b(0xA9),b(0x0A),b(0xB9),b(0xF9),b(0x45),b(0xA9),b(0x0A),b(0x25),b(0xA9),b(0xC5),b(0x25),b(0x0A),b(0x29),b(0x89), @@ -484,5 +485,5 @@ public class ComplexColumnTest extends TestCase }; private static final byte[] TEST2_BYTES = getAsciiBytes("this is some more test data for attachment."); - + } diff --git a/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java b/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java index 025e180..1aa2483 100644 --- a/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/DatabaseTest.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -51,7 +52,8 @@ import static com.healthmarketscience.jackcess.TestUtil.*; /** * @author Tim McCune */ -public class DatabaseTest extends TestCase +@SuppressWarnings("deprecation") +public class DatabaseTest extends TestCase { public DatabaseTest(String name) throws Exception { super(name); @@ -114,7 +116,7 @@ public class DatabaseTest extends TestCase db.close(); } } - + public void testReadDeletedRows() throws Exception { for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.DEL, true)) { Table table = open(testDB).getTable("Table"); @@ -122,11 +124,11 @@ public class DatabaseTest extends TestCase while (table.getNextRow() != null) { rows++; } - assertEquals(2, rows); + assertEquals(2, rows); table.getDatabase().close(); } } - + public void testGetColumns() throws Exception { for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) { @@ -143,9 +145,9 @@ public class DatabaseTest extends TestCase checkColumn(columns, 8, "I", DataType.BOOLEAN); } } - + private static void checkColumn( - List<? extends Column> columns, int columnNumber, String name, + List<? extends Column> columns, int columnNumber, String name, DataType dataType) throws Exception { @@ -153,7 +155,7 @@ public class DatabaseTest extends TestCase assertEquals(name, column.getName()); assertEquals(dataType, column.getType()); } - + public void testGetNextRow() throws Exception { for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) { final Database db = open(testDB); @@ -183,7 +185,7 @@ public class DatabaseTest extends TestCase db.close(); } } - + public void testDeleteCurrentRow() throws Exception { // make sure correct row is deleted @@ -269,7 +271,7 @@ public class DatabaseTest extends TestCase table.reset(); List<Row> rows = RowFilterTest.toList(table); - + Row r1 = rows.remove(7); Row r2 = rows.remove(3); assertEquals(8, rows.size()); @@ -282,10 +284,10 @@ public class DatabaseTest extends TestCase table.deleteRow(r2); table.deleteRow(r1); - assertTable(rows, table); + assertTable(rows, table); } } - + public void testMissingFile() throws Exception { File bogusFile = new File("fooby-dooby.mdb"); assertTrue(!bogusFile.exists()); @@ -326,7 +328,7 @@ public class DatabaseTest extends TestCase } rowNum++; } - + table.getDatabase().close(); } } @@ -508,7 +510,7 @@ public class DatabaseTest extends TestCase db.close(); } - } + } public void testMultiPageTableDef() throws Exception { @@ -580,7 +582,7 @@ public class DatabaseTest extends TestCase db.close(); } - } + } public void testLargeTableDef() throws Exception { @@ -685,8 +687,8 @@ public class DatabaseTest extends TestCase TimeZone tz = TimeZone.getTimeZone("America/New_York"); SimpleDateFormat sdf = DatabaseBuilder.createDateFormat("yyyy-MM-dd"); sdf.getCalendar().setTimeZone(tz); - - List<String> dates = Arrays.asList("1582-10-15", "1582-10-14", + + List<String> dates = Arrays.asList("1582-10-15", "1582-10-14", "1492-01-10", "1392-01-10"); @@ -703,7 +705,7 @@ public class DatabaseTest extends TestCase Date d = sdf.parse(dateStr); table.addRow("row " + dateStr, d); } - + List<String> foundDates = new ArrayList<String>(); for(Row row : table) { foundDates.add(sdf.format(row.getDate("date"))); @@ -741,7 +743,7 @@ public class DatabaseTest extends TestCase sysTables.addAll( Arrays.asList("MSysObjects", "MSysQueries", "MSysACES", "MSysRelationships")); - + if (fileFormat == FileFormat.GENERIC_JET4) { assertNull("file format: " + fileFormat, db.getSystemTable("MSysAccessObjects")); } else if (fileFormat.ordinal() < FileFormat.V2003.ordinal()) { @@ -766,11 +768,11 @@ public class DatabaseTest extends TestCase if(fileFormat.ordinal() >= FileFormat.V2010.ordinal()) { sysTables.add("f_12D7448B56564D8AAE333BCC9B3718E5_Data"); sysTables.add("MSysResources"); - } + } } assertEquals(sysTables, db.getSystemTableNames()); - + assertNotNull(db.getSystemTable("MSysObjects")); assertNotNull(db.getSystemTable("MSysQueries")); assertNotNull(db.getSystemTable("MSysACES")); @@ -782,7 +784,7 @@ public class DatabaseTest extends TestCase assertEquals("MSysObjects", tmd.getName()); assertFalse(tmd.isLinked()); assertTrue(tmd.isSystem()); - + db.close(); } } @@ -839,7 +841,7 @@ public class DatabaseTest extends TestCase "RawData[(12) FF FE 6F 74 68 65 72 20 64 61 74 61]", null); List<String> fixVals = Arrays.asList("RawData[(4) 37 00 00 00]", - "RawData[(4) F3 FF FF FF]", + "RawData[(4) F3 FF FF FF]", "RawData[(4) 02 00 00 00]"); int idx = 0; @@ -891,7 +893,8 @@ public class DatabaseTest extends TestCase Database linkeeDb = db.getLinkedDatabases().get(linkeeDbName); assertNotNull(linkeeDb); assertEquals(linkeeFile, linkeeDb.getFile()); - + assertEquals("linkeeTest.accdb", ((DatabaseImpl)linkeeDb).getName()); + List<? extends Map<String, Object>> expectedRows = createExpectedTable( createExpectedRow( @@ -900,7 +903,7 @@ public class DatabaseTest extends TestCase assertTable(expectedRows, t2); - db.createLinkedTable("FooTable", linkeeDbName, "Table2"); + db.createLinkedTable("FooTable", linkeeDbName, "Table2"); tmd = db.getTableMetaData("FooTable"); assertEquals("FooTable", tmd.getName()); @@ -929,7 +932,7 @@ public class DatabaseTest extends TestCase assertNull(tmd.getLinkedDbName()); Table t1 = tmd.open(db); - + assertFalse(db.isLinkedTable(null)); assertTrue(db.isLinkedTable(t2)); assertTrue(db.isLinkedTable(t3)); @@ -941,21 +944,21 @@ public class DatabaseTest extends TestCase assertTrue(tables.contains(t2)); assertTrue(tables.contains(t3)); assertFalse(tables.contains(((DatabaseImpl)db).getSystemCatalog())); - + tables = getTables(db.newIterable().setIncludeNormalTables(false)); assertEquals(2, tables.size()); assertFalse(tables.contains(t1)); assertTrue(tables.contains(t2)); assertTrue(tables.contains(t3)); assertFalse(tables.contains(((DatabaseImpl)db).getSystemCatalog())); - + tables = getTables(db.newIterable().withLocalUserTablesOnly()); assertEquals(1, tables.size()); assertTrue(tables.contains(t1)); assertFalse(tables.contains(t2)); assertFalse(tables.contains(t3)); assertFalse(tables.contains(((DatabaseImpl)db).getSystemCatalog())); - + tables = getTables(db.newIterable().withSystemTablesOnly()); assertTrue(tables.size() > 5); assertFalse(tables.contains(t1)); @@ -975,7 +978,7 @@ public class DatabaseTest extends TestCase } return tableList; } - + public void testTimeZone() throws Exception { TimeZone tz = TimeZone.getTimeZone("America/New_York"); @@ -989,7 +992,9 @@ public class DatabaseTest extends TestCase { ColumnImpl col = new ColumnImpl(null, null, DataType.SHORT_DATE_TIME, 0, 0, 0) { @Override - protected Calendar getCalendar() { return Calendar.getInstance(tz); } + public TimeZone getTimeZone() { return tz; } + @Override + public ZoneId getZoneId() { return null; } }; SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd"); diff --git a/src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java b/src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java new file mode 100644 index 0000000..637629a --- /dev/null +++ b/src/test/java/com/healthmarketscience/jackcess/LocalDateTimeTest.java @@ -0,0 +1,160 @@ +/* +Copyright (c) 2018 James Ahlborn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.healthmarketscience.jackcess; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.math.BigDecimal; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; +import java.util.UUID; + +import com.healthmarketscience.jackcess.impl.ColumnImpl; +import com.healthmarketscience.jackcess.impl.DatabaseImpl; +import com.healthmarketscience.jackcess.impl.RowIdImpl; +import com.healthmarketscience.jackcess.impl.RowImpl; +import com.healthmarketscience.jackcess.impl.TableImpl; +import com.healthmarketscience.jackcess.util.LinkResolver; +import junit.framework.TestCase; +import static com.healthmarketscience.jackcess.TestUtil.*; +import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; +import static com.healthmarketscience.jackcess.Database.*; + +/** + * + * @author James Ahlborn + */ +public class LocalDateTimeTest extends TestCase +{ + public LocalDateTimeTest(String name) throws Exception { + super(name); + } + + public void testAncientDates() throws Exception + { + ZoneId zoneId = ZoneId.of("America/New_York"); + DateTimeFormatter sdf = DateTimeFormatter.ofPattern("uuuu-MM-dd"); + + List<String> dates = Arrays.asList("1582-10-15", "1582-10-14", + "1492-01-10", "1392-01-10"); + + for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) { + Database db = createMem(fileFormat); + db.setZoneId(zoneId); + db.setDateTimeType(DateTimeType.LOCAL_DATE_TIME); + + Table table = new TableBuilder("test") + .addColumn(new ColumnBuilder("name", DataType.TEXT)) + .addColumn(new ColumnBuilder("date", DataType.SHORT_DATE_TIME)) + .toTable(db); + + for(String dateStr : dates) { + LocalDate ld = LocalDate.parse(dateStr, sdf); + table.addRow("row " + dateStr, ld); + } + + List<String> foundDates = new ArrayList<String>(); + for(Row row : table) { + foundDates.add(sdf.format(row.getLocalDateTime("date"))); + } + + assertEquals(dates, foundDates); + + db.close(); + } + + for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.OLD_DATES)) { + Database db = openCopy(testDB); + db.setDateTimeType(DateTimeType.LOCAL_DATE_TIME); + + Table t = db.getTable("Table1"); + + List<String> foundDates = new ArrayList<String>(); + for(Row row : t) { + foundDates.add(sdf.format(row.getLocalDateTime("DateField"))); + } + + assertEquals(dates, foundDates); + + db.close(); + } + + } + + public void testZoneId() throws Exception + { + ZoneId zoneId = ZoneId.of("America/New_York"); + doTestZoneId(zoneId); + + zoneId = ZoneId.of("Australia/Sydney"); + doTestZoneId(zoneId); + } + + private static void doTestZoneId(final ZoneId zoneId) throws Exception + { + final TimeZone tz = TimeZone.getTimeZone(zoneId); + ColumnImpl col = new ColumnImpl(null, null, DataType.SHORT_DATE_TIME, 0, 0, 0) { + @Override + public TimeZone getTimeZone() { return tz; } + @Override + public ZoneId getZoneId() { return zoneId; } + }; + + SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd"); + df.setTimeZone(tz); + + long startDate = df.parse("2012.01.01").getTime(); + long endDate = df.parse("2013.01.01").getTime(); + + Calendar curCal = Calendar.getInstance(tz); + curCal.setTimeInMillis(startDate); + + DateTimeFormatter sdf = DateTimeFormatter.ofPattern("uuuu.MM.dd HH:mm:ss"); + + while(curCal.getTimeInMillis() < endDate) { + Date curDate = curCal.getTime(); + LocalDateTime curLdt = LocalDateTime.ofInstant( + Instant.ofEpochMilli(curDate.getTime()), zoneId); + LocalDateTime newLdt = ColumnImpl.ldtFromLocalDateDouble( + col.toDateDouble(curDate)); + if(!curLdt.equals(newLdt)) { + System.out.println("FOO " + curLdt + " " + newLdt); + assertEquals(sdf.format(curLdt), sdf.format(newLdt)); + } + curCal.add(Calendar.MINUTE, 30); + } + } + +} diff --git a/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java index 384386e..bb26719 100644 --- a/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/PropertyExpressionTest.java @@ -294,8 +294,8 @@ public class PropertyExpressionTest extends TestCase public static void testCustomEvalConfig() throws Exception { - TemporalConfig tempConf = new TemporalConfig("yyyy/M/d", "M/d", - "yyyy-MMM-d", + TemporalConfig tempConf = new TemporalConfig("[uuuu/]M/d", + "uuuu-MMM-d", "hh.mm.ss a", "HH.mm.ss", '/', '.', Locale.US); diff --git a/src/test/java/com/healthmarketscience/jackcess/TableTest.java b/src/test/java/com/healthmarketscience/jackcess/TableTest.java index 339ba39..eaae617 100644 --- a/src/test/java/com/healthmarketscience/jackcess/TableTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/TableTest.java @@ -21,8 +21,8 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.List; +import java.util.TimeZone; import com.healthmarketscience.jackcess.impl.ColumnImpl; import com.healthmarketscience.jackcess.impl.JetFormat; @@ -40,8 +40,8 @@ public class TableTest extends TestCase { private TestTable _testTable; private int _varLenIdx; private int _fixedOffset; - - + + public TableTest(String name) { super(name); } @@ -52,14 +52,14 @@ public class TableTest extends TestCase { _varLenIdx = 0; _fixedOffset = 0; } - + public void testCreateRow() throws Exception { reset(); newTestColumn(DataType.INT, false); newTestColumn(DataType.TEXT, false); newTestColumn(DataType.TEXT, false); newTestTable(); - + int colCount = _columns.size(); ByteBuffer buffer = createRow(9, "Tim", "McCune"); @@ -91,10 +91,10 @@ public class TableTest extends TestCase { newTestColumn(DataType.TEXT, true); newTestColumn(DataType.TEXT, true); newTestTable(); - + ByteBuffer[] bufCmp1 = encodeColumns(small, large); ByteBuffer[] bufCmp2 = encodeColumns(smallNotAscii, largeNotAscii); - + assertEquals(buf1[0].remaining(), (bufCmp1[0].remaining() + small.length() - 2)); assertEquals(buf1[1].remaining(), @@ -111,7 +111,7 @@ public class TableTest extends TestCase { } - private ByteBuffer createRow(Object... row) + private ByteBuffer createRow(Object... row) throws IOException { return _testTable.createRow(row); @@ -146,7 +146,7 @@ public class TableTest extends TestCase { return b; } - private TableImpl newTestTable() + private TableImpl newTestTable() throws Exception { _testTable = new TestTable(); @@ -185,8 +185,8 @@ public class TableTest extends TestCase { return getFormat().CHARSET; } @Override - protected Calendar getCalendar() { - return Calendar.getInstance(); + public TimeZone getTimeZone() { + return TimeZone.getDefault(); } @Override public boolean isCompressedUnicode() { diff --git a/src/test/java/com/healthmarketscience/jackcess/TestUtil.java b/src/test/java/com/healthmarketscience/jackcess/TestUtil.java index 7680fb3..b5db277 100644 --- a/src/test/java/com/healthmarketscience/jackcess/TestUtil.java +++ b/src/test/java/com/healthmarketscience/jackcess/TestUtil.java @@ -53,6 +53,7 @@ import org.junit.Assert; * * @author James Ahlborn */ +@SuppressWarnings("deprecation") public class TestUtil { public static final TimeZone TEST_TZ = @@ -86,7 +87,7 @@ public class TestUtil throws Exception { FileChannel channel = (inMem ? MemFileChannel.newChannel( - file, DatabaseImpl.RW_CHANNEL_MODE) + file, MemFileChannel.RW_CHANNEL_MODE) : null); final Database db = new DatabaseBuilder(file).setReadOnly(true) .setAutoSync(getTestAutoSync()).setChannel(channel).open(); diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java index 561f1e8..104b266 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/DatabaseReadWriteTest.java @@ -16,6 +16,9 @@ limitations under the License. package com.healthmarketscience.jackcess.impl; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -53,7 +56,7 @@ public class DatabaseReadWriteTest extends TestCase db.close(); } } - + public void testWriteAndReadInMem() throws Exception { for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) { Database db = createMem(fileFormat); @@ -61,7 +64,7 @@ public class DatabaseReadWriteTest extends TestCase db.close(); } } - + private static void doTestWriteAndRead(Database db) throws Exception { createTestTable(db); Object[] row = createTestRow(); @@ -117,7 +120,7 @@ public class DatabaseReadWriteTest extends TestCase } } - public void testUpdateRow() throws Exception + public void testUpdateRow() throws Exception { for (final FileFormat fileFormat : SUPPORTED_FILEFORMATS) { Database db = createMem(fileFormat); @@ -250,11 +253,18 @@ public class DatabaseReadWriteTest extends TestCase final long timeRange = 100000000L; final long timeStep = 37L; - for(long time = testTime - timeRange; time < testTime + timeRange; + for(long time = testTime - timeRange; time < testTime + timeRange; time += timeStep) { double accTime = ColumnImpl.toLocalDateDouble(time); long newTime = ColumnImpl.fromLocalDateDouble(accTime); assertEquals(time, newTime); + + Instant inst = Instant.ofEpochMilli(time); + LocalDateTime ldt = LocalDateTime.ofInstant(inst, ZoneOffset.UTC); + + accTime = ColumnImpl.toDateDouble(ldt); + LocalDateTime newLdt = ColumnImpl.ldtFromLocalDateDouble(accTime); + assertEquals(ldt, newLdt); } } } diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java index 1ed17b7..36ab9bd 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/JetFormatTest.java @@ -98,7 +98,7 @@ public class JetFormatTest extends TestCase { } SUPPORTED_FILEFORMATS = supported.toArray(new FileFormat[0]); - SUPPORTED_FILEFORMATS_FOR_READ = + SUPPORTED_FILEFORMATS_FOR_READ = supportedForRead.toArray(new FileFormat[0]); } @@ -110,7 +110,7 @@ public class JetFormatTest extends TestCase { private final File dbFile; private final FileFormat expectedFileFormat; - private TestDB(File databaseFile, + private TestDB(File databaseFile, FileFormat expectedDBFileFormat) { dbFile = databaseFile; @@ -119,12 +119,12 @@ public class JetFormatTest extends TestCase { public final File getFile() { return dbFile; } - public final FileFormat getExpectedFileFormat() { - return expectedFileFormat; + public final FileFormat getExpectedFileFormat() { + return expectedFileFormat; } - public final JetFormat getExpectedFormat() { - return DatabaseImpl.getFileFormatDetails(expectedFileFormat).getFormat(); + public final JetFormat getExpectedFormat() { + return DatabaseImpl.getFileFormatDetails(expectedFileFormat).getFormat(); } @Override @@ -141,14 +141,14 @@ public class JetFormatTest extends TestCase { boolean readOnly) { List<TestDB> supportedTestDBs = new ArrayList<TestDB>(); - for (FileFormat fileFormat : + for (FileFormat fileFormat : (readOnly ? SUPPORTED_FILEFORMATS_FOR_READ : SUPPORTED_FILEFORMATS)) { File testFile = getFileForBasename(basename, fileFormat); if(!testFile.exists()) { continue; } - + // verify that the db is the file format expected try { Database db = new DatabaseBuilder(testFile).setReadOnly(true).open(); @@ -170,16 +170,16 @@ public class JetFormatTest extends TestCase { private static File getFileForBasename( Basename basename, FileFormat fileFormat) { - return new File(DIR_TEST_DATA, + return new File(DIR_TEST_DATA, fileFormat.name() + File.separator + - basename + fileFormat.name() + + basename + fileFormat.name() + fileFormat.getFileExtension()); } } - public static final List<TestDB> SUPPORTED_DBS_TEST = + public static final List<TestDB> SUPPORTED_DBS_TEST = TestDB.getSupportedForBasename(Basename.TEST); - public static final List<TestDB> SUPPORTED_DBS_TEST_FOR_READ = + public static final List<TestDB> SUPPORTED_DBS_TEST_FOR_READ = TestDB.getSupportedForBasename(Basename.TEST, true); @@ -193,11 +193,12 @@ public class JetFormatTest extends TestCase { for (final TestDB testDB : SUPPORTED_DBS_TEST_FOR_READ) { - final FileChannel channel = DatabaseImpl.openChannel(testDB.dbFile, false); + final FileChannel channel = DatabaseImpl.openChannel( + testDB.dbFile.toPath(), false); try { JetFormat fmtActual = JetFormat.getFormat(channel); - assertEquals("Unexpected JetFormat for dbFile: " + + assertEquals("Unexpected JetFormat for dbFile: " + testDB.dbFile.getAbsolutePath(), testDB.getExpectedFormat(), fmtActual); @@ -221,7 +222,7 @@ public class JetFormatTest extends TestCase { PropertyMap props = db.getUserDefinedProperties(); props.put("foo", "bar"); props.save(); - } + } } catch(Exception e) { failure = e; @@ -268,7 +269,7 @@ public class JetFormatTest extends TestCase { } public void testSqlTypes() throws Exception { - + JetFormat v2000 = JetFormat.VERSION_4; for(DataType dt : DataType.values()) { if(v2000.isSupportedDataType(dt)) { diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java index 132b788..895bbed 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctionsTest.java @@ -17,6 +17,7 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.Calendar; import java.util.Date; @@ -78,9 +79,9 @@ public class DefaultFunctionsTest extends TestCase eval("=CSng(\"57.12345\")")); assertEval("9786", "=CStr(9786)"); assertEval("-42", "=CStr(-42)"); - assertEval(new Date(1041483600000L), "=CDate('01/02/2003')"); - assertEval(new Date(1041508800000L), "=CDate('01/02/2003 7:00:00 AM')"); - assertEval(new Date(-1948781520000L), "=CDate(3013.45)"); + assertEval(LocalDateTime.of(2003,1,2,0,0), "=CDate('01/02/2003')"); + assertEval(LocalDateTime.of(2003,1,2,7,0), "=CDate('01/02/2003 7:00:00 AM')"); + assertEval(LocalDateTime.of(1908,3,31,10,48), "=CDate(3013.45)"); assertEval(-1, "=IsNull(Null)"); @@ -589,7 +590,7 @@ public class DefaultFunctionsTest extends TestCase assertEval("409.090909090909", "=CStr(SYD(30000,7500,10,10))"); assertEval("-1.63048347266756E-02", "=CStr(Rate(3,200,-610,0,-20,0.1))"); - assertEval("7.70147248820175E-03", "=CStr(Rate(4*12,-200,8000))"); + assertEval("7.70147248820165E-03", "=CStr(Rate(4*12,-200,8000))"); assertEval("-1.09802980531205", "=CStr(Rate(60,93.22,5000,0.1))"); } diff --git a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java index c67dfe7..67ad20b 100644 --- a/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/impl/expr/ExpressionatorTest.java @@ -20,14 +20,13 @@ import java.io.BufferedReader; import java.io.FileReader; import java.math.BigDecimal; import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import javax.script.Bindings; import javax.script.SimpleBindings; import com.healthmarketscience.jackcess.DataType; -import com.healthmarketscience.jackcess.DatabaseBuilder; import com.healthmarketscience.jackcess.TestUtil; import com.healthmarketscience.jackcess.expr.EvalContext; import com.healthmarketscience.jackcess.expr.Expression; @@ -38,7 +37,6 @@ import com.healthmarketscience.jackcess.expr.ParseException; import com.healthmarketscience.jackcess.expr.TemporalConfig; import com.healthmarketscience.jackcess.expr.Value; import com.healthmarketscience.jackcess.impl.BaseEvalContext; -import com.healthmarketscience.jackcess.impl.expr.NumberFormatter; import junit.framework.TestCase; /** @@ -318,11 +316,11 @@ public class ExpressionatorTest extends TestCase public void testDateArith() throws Exception { - assertEquals(new Date(1041508800000L), eval("=#01/02/2003# + #7:00:00 AM#")); - assertEquals(new Date(1041458400000L), eval("=#01/02/2003# - #7:00:00 AM#")); - assertEquals(new Date(1044680400000L), eval("=#01/02/2003# + '37'")); - assertEquals(new Date(1044680400000L), eval("='37' + #01/02/2003#")); - assertEquals(new Date(1041508800000L), eval("=#01/02/2003 7:00:00 AM#")); + assertEquals(LocalDateTime.of(2003,1,2,7,0), eval("=#01/02/2003# + #7:00:00 AM#")); + assertEquals(LocalDateTime.of(2003,1,1,17,0), eval("=#01/02/2003# - #7:00:00 AM#")); + assertEquals(LocalDateTime.of(2003,2,8,0,0), eval("=#01/02/2003# + '37'")); + assertEquals(LocalDateTime.of(2003,2,8,0,0), eval("='37' + #01/02/2003#")); + assertEquals(LocalDateTime.of(2003,1,2,7,0), eval("=#01/02/2003 7:00:00 AM#")); assertEquals("2/8/2003", eval("=CStr(#01/02/2003# + '37')")); assertEquals("9:24:00 AM", eval("=CStr(#7:00:00 AM# + 0.1)")); @@ -404,7 +402,7 @@ public class ExpressionatorTest extends TestCase assertEquals("foo37", eval("=\"foo\" + (12 + 25)")); assertEquals("25foo12", eval("=\"25foo\" + 12")); - assertEquals(new Date(1485579600000L), eval("=#1/1/2017# + 27")); + assertEquals(LocalDateTime.of(2017,1,28,0,0), eval("=#1/1/2017# + 27")); assertEquals(128208, eval("=#1/1/2017# * 3")); } @@ -590,15 +588,14 @@ public class ExpressionatorTest extends TestCase return TemporalConfig.US_TEMPORAL_CONFIG; } - public SimpleDateFormat createDateFormat(String formatStr) { - SimpleDateFormat sdf = DatabaseBuilder.createDateFormat(formatStr); - sdf.setTimeZone(TestUtil.TEST_TZ); - return sdf; + public DateTimeFormatter createDateFormatter(String formatStr) { + DateTimeFormatter dtf = DateTimeFormatter.ofPattern( + formatStr, TemporalConfig.US_TEMPORAL_CONFIG.getLocale()); + return dtf; } - public Calendar getCalendar() { - return createDateFormat(getTemporalConfig().getDefaultDateTimeFormat()) - .getCalendar(); + public ZoneId getZoneId() { + return TestUtil.TEST_TZ.toZoneId(); } public NumericConfig getNumericConfig() { diff --git a/src/test/java/com/healthmarketscience/jackcess/query/QueryTest.java b/src/test/java/com/healthmarketscience/jackcess/query/QueryTest.java index f7e48f4..f2fe675 100644 --- a/src/test/java/com/healthmarketscience/jackcess/query/QueryTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/query/QueryTest.java @@ -29,9 +29,8 @@ import com.healthmarketscience.jackcess.TestUtil; import com.healthmarketscience.jackcess.impl.query.QueryImpl; import com.healthmarketscience.jackcess.impl.query.QueryImpl.Row; import junit.framework.TestCase; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; -import static org.apache.commons.lang.SystemUtils.LINE_SEPARATOR; import static com.healthmarketscience.jackcess.impl.query.QueryFormat.*; import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; @@ -53,22 +52,22 @@ public class QueryTest extends TestCase String expr2 = "Select * from Table2"; UnionQuery query = (UnionQuery)newQuery( - Query.Type.UNION, + Query.Type.UNION, newRow(TABLE_ATTRIBUTE, expr1, null, UNION_PART1), newRow(TABLE_ATTRIBUTE, expr2, null, UNION_PART2)); setFlag(query, 3); assertEquals(multiline("Select * from Table1", - "UNION Select * from Table2;"), + "UNION Select * from Table2;"), query.toSQLString()); setFlag(query, 1); assertEquals(multiline("Select * from Table1", - "UNION ALL Select * from Table2;"), + "UNION ALL Select * from Table2;"), query.toSQLString()); - addRows(query, newRow(ORDERBY_ATTRIBUTE, "Table1.id", + addRows(query, newRow(ORDERBY_ATTRIBUTE, "Table1.id", null, null)); assertEquals(multiline("Select * from Table1", @@ -112,7 +111,7 @@ public class QueryTest extends TestCase public void testUpdateQuery() throws Exception { UpdateQuery query = (UpdateQuery)newQuery( - Query.Type.UPDATE, + Query.Type.UPDATE, newRow(TABLE_ATTRIBUTE, null, "Table1", null), newRow(COLUMN_ATTRIBUTE, "\"some string\"", null, "Table1.id"), newRow(COLUMN_ATTRIBUTE, "42", null, "Table1.col1")); @@ -122,7 +121,7 @@ public class QueryTest extends TestCase "SET Table1.id = \"some string\", Table1.col1 = 42;"), query.toSQLString()); - addRows(query, newRow(WHERE_ATTRIBUTE, "(Table1.col2 < 13)", + addRows(query, newRow(WHERE_ATTRIBUTE, "(Table1.col2 < 13)", null, null)); assertEquals( @@ -135,14 +134,14 @@ public class QueryTest extends TestCase public void testSelectQuery() throws Exception { SelectQuery query = (SelectQuery)newQuery( - Query.Type.SELECT, + Query.Type.SELECT, newRow(TABLE_ATTRIBUTE, null, "Table1", null)); setFlag(query, 1); assertEquals(multiline("SELECT *", "FROM Table1;"), query.toSQLString()); - + doTestColumns(query); doTestSelectFlags(query); doTestParameters(query); @@ -168,7 +167,7 @@ public class QueryTest extends TestCase } addRows(query, newRow(TYPE_ATTRIBUTE, null, -1, null, null)); - + try { query.getTypeRow(); fail("IllegalStateException should have been thrown"); @@ -265,7 +264,7 @@ public class QueryTest extends TestCase newRow(COLUMN_ATTRIBUTE, "54", APPEND_VALUE_FLAG, null, null), newRow(COLUMN_ATTRIBUTE, "'hello'", APPEND_VALUE_FLAG, null, null)); - assertEquals(multiline("INSERT INTO Table2", + assertEquals(multiline("INSERT INTO Table2", "VALUES (54, 'hello');"), query.toSQLString()); query = (AppendQuery)newQuery( @@ -274,7 +273,7 @@ public class QueryTest extends TestCase newRow(COLUMN_ATTRIBUTE, "54", APPEND_VALUE_FLAG, null, "ID"), newRow(COLUMN_ATTRIBUTE, "'hello'", APPEND_VALUE_FLAG, null, "Field 3")); - assertEquals(multiline("INSERT INTO Table2 (ID, [Field 3])", + assertEquals(multiline("INSERT INTO Table2 (ID, [Field 3])", "VALUES (54, 'hello');"), query.toSQLString()); } @@ -291,27 +290,27 @@ public class QueryTest extends TestCase private void doTestSelectFlags(SelectQuery query) throws Exception { setFlag(query, 3); - + assertEquals(multiline("SELECT DISTINCT Table1.id, Table1.col AS [Some.Alias], *", "FROM Table1;"), query.toSQLString()); setFlag(query, 9); - + assertEquals(multiline("SELECT DISTINCTROW Table1.id, Table1.col AS [Some.Alias], *", "FROM Table1;"), query.toSQLString()); setFlag(query, 7); - + assertEquals(multiline("SELECT DISTINCT Table1.id, Table1.col AS [Some.Alias], *", "FROM Table1", "WITH OWNERACCESS OPTION;"), query.toSQLString()); - replaceRows(query, + replaceRows(query, newRow(FLAG_ATTRIBUTE, null, 49, null, "5", null)); - + assertEquals(multiline("SELECT TOP 5 PERCENT Table1.id, Table1.col AS [Some.Alias], *", "FROM Table1;"), query.toSQLString()); @@ -354,7 +353,7 @@ public class QueryTest extends TestCase assertEquals(multiline("SELECT Table1.id, Table1.col AS [Some.Alias]", "FROM Table1, Table2 AS [Another Table], [Select val from Table3].val AS Table3Val;"), - query.toSQLString()); + query.toSQLString()); } private void doTestRemoteDb(SelectQuery query) throws Exception @@ -387,7 +386,7 @@ public class QueryTest extends TestCase assertEquals(multiline("SELECT Table1.id, Table1.col AS [Some.Alias]", "FROM [Select val from Table3].val AS Table3Val, Table1 INNER JOIN Table2 AS [Another Table] ON (Table1.id = [Another Table].id);"), query.toSQLString()); - + addRows(query, newRow(JOIN_ATTRIBUTE, "(Table1.id = Table3Val.id)", 2, "Table1", "Table3Val")); assertEquals(multiline("SELECT Table1.id, Table1.col AS [Some.Alias]", @@ -417,13 +416,13 @@ public class QueryTest extends TestCase private void doTestWhereExpression(SelectQuery query) throws Exception { - addRows(query, newRow(WHERE_ATTRIBUTE, "(Table1.col2 < 13)", + addRows(query, newRow(WHERE_ATTRIBUTE, "(Table1.col2 < 13)", null, null)); assertEquals(multiline("SELECT Table1.id, Table1.col AS [Some.Alias]", "FROM Table1, Table2 AS [Another Table], [Select val from Table3].val AS Table3Val", "WHERE (Table1.col2 < 13);"), - query.toSQLString()); + query.toSQLString()); } private void doTestGroupings(SelectQuery query) throws Exception @@ -435,7 +434,7 @@ public class QueryTest extends TestCase "FROM Table1, Table2 AS [Another Table], [Select val from Table3].val AS Table3Val", "WHERE (Table1.col2 < 13)", "GROUP BY Table1.id, SUM(Table1.val);"), - query.toSQLString()); + query.toSQLString()); } private void doTestHavingExpression(SelectQuery query) throws Exception @@ -447,7 +446,7 @@ public class QueryTest extends TestCase "WHERE (Table1.col2 < 13)", "GROUP BY Table1.id, SUM(Table1.val)", "HAVING (SUM(Table1.val) = 500);"), - query.toSQLString()); + query.toSQLString()); } private void doTestOrderings(SelectQuery query) throws Exception @@ -461,7 +460,7 @@ public class QueryTest extends TestCase "GROUP BY Table1.id, SUM(Table1.val)", "HAVING (SUM(Table1.val) = 500)", "ORDER BY Table1.id, Table2.val DESC;"), - query.toSQLString()); + query.toSQLString()); } public void testComplexJoins() throws Exception @@ -484,7 +483,7 @@ public class QueryTest extends TestCase addJoinRows(query, 1, 2, 1, 2, 1, 1); - + assertEquals(multiline("SELECT *", "FROM Table3, Table4, Table5, Table6, Table7, Table8, Table9, Table10, Table1 INNER JOIN Table2 ON (Table2.f3 = Table1.f3) AND (Table1.f0 = Table2.f0);"), query.toSQLString()); @@ -498,7 +497,7 @@ public class QueryTest extends TestCase } catch(IllegalStateException e) { // success } - + addJoinRows(query, 1, 2, 1, 3, 4, 1, 5, 6, 1, @@ -613,7 +612,7 @@ public class QueryTest extends TestCase private static void setFlag(Query query, Number newFlagNum) { - replaceRows(query, + replaceRows(query, newRow(FLAG_ATTRIBUTE, null, newFlagNum, null, null, null)); } @@ -646,7 +645,7 @@ public class QueryTest extends TestCase private static String multiline(String... strs) { - return StringUtils.join(strs, LINE_SEPARATOR); + return StringUtils.join(strs, System.lineSeparator()); } } diff --git a/src/test/java/com/healthmarketscience/jackcess/util/CustomLinkResolverTest.java b/src/test/java/com/healthmarketscience/jackcess/util/CustomLinkResolverTest.java index 87f8e6f..d0446b2 100644 --- a/src/test/java/com/healthmarketscience/jackcess/util/CustomLinkResolverTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/util/CustomLinkResolverTest.java @@ -16,9 +16,9 @@ limitations under the License. package com.healthmarketscience.jackcess.util; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.Path; import com.healthmarketscience.jackcess.ColumnBuilder; import com.healthmarketscience.jackcess.DataType; @@ -149,7 +149,7 @@ public class CustomLinkResolverTest extends TestCase @Override protected Database createTempDb(Object customFile, FileFormat format, - boolean inMemory, File tempDir, + boolean inMemory, Path tempDir, boolean readOnly) throws IOException { diff --git a/src/test/java/com/healthmarketscience/jackcess/util/ExportTest.java b/src/test/java/com/healthmarketscience/jackcess/util/ExportTest.java index 754be06..5ffdbd9 100644 --- a/src/test/java/com/healthmarketscience/jackcess/util/ExportTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/util/ExportTest.java @@ -30,7 +30,6 @@ import com.healthmarketscience.jackcess.Table; import com.healthmarketscience.jackcess.TableBuilder; import com.healthmarketscience.jackcess.impl.JetFormatTest; import junit.framework.TestCase; -import org.apache.commons.lang.SystemUtils; import static com.healthmarketscience.jackcess.TestUtil.*; /** @@ -39,7 +38,7 @@ import static com.healthmarketscience.jackcess.TestUtil.*; */ public class ExportTest extends TestCase { - private static final String NL = SystemUtils.LINE_SEPARATOR; + private static final String NL = System.lineSeparator(); public ExportTest(String name) { @@ -78,7 +77,7 @@ public class ExportTest extends TestCase new ExportUtil.Builder(db, "test") .exportWriter(new BufferedWriter(out)); - String expected = + String expected = "some text||some more,13,13.25,\"61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78\n79 7A 61 62 63 64\",true," + testDate + NL + "\"crazy'data\"\"here\",-345,-3.45E-4,61 62 63 64 65 66 67,true," + NL + "C:\\temp\\some_file.txt,25,0.0,,false," + NL; @@ -86,14 +85,14 @@ public class ExportTest extends TestCase assertEquals(expected, out.toString()); out = new StringWriter(); - + new ExportUtil.Builder(db, "test") .setHeader(true) .setDelimiter("||") .setQuote('\'') .exportWriter(new BufferedWriter(out)); - expected = + expected = "col1||col2||col3||col4||col5||col6" + NL + "'some text||some more'||13||13.25||'61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78\n79 7A 61 62 63 64'||true||" + testDate + NL + "'crazy''data\"here'||-345||-3.45E-4||61 62 63 64 65 66 67||true||" + NL + @@ -117,7 +116,7 @@ public class ExportTest extends TestCase .setFilter(oddFilter) .exportWriter(new BufferedWriter(out)); - expected = + expected = "some text||some more,13,13.25,\"61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78\n79 7A 61 62 63 64\",true," + testDate + NL + "C:\\temp\\some_file.txt,25,0.0,,false," + NL; diff --git a/src/test/java/com/healthmarketscience/jackcess/util/OleBlobTest.java b/src/test/java/com/healthmarketscience/jackcess/util/OleBlobTest.java index be3a0a9..725daa0 100644 --- a/src/test/java/com/healthmarketscience/jackcess/util/OleBlobTest.java +++ b/src/test/java/com/healthmarketscience/jackcess/util/OleBlobTest.java @@ -34,7 +34,7 @@ import com.healthmarketscience.jackcess.impl.CompoundOleUtil; import junit.framework.TestCase; import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.DocumentInputStream; -import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; import static com.healthmarketscience.jackcess.TestUtil.*; import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; @@ -42,7 +42,7 @@ import static com.healthmarketscience.jackcess.impl.JetFormatTest.*; * * @author James Ahlborn */ -public class OleBlobTest extends TestCase +public class OleBlobTest extends TestCase { public OleBlobTest(String name) { @@ -73,7 +73,7 @@ public class OleBlobTest extends TestCase } finally { ByteUtil.closeQuietly(blob); } - + try { blob = new OleBlob.Builder() .setLink(sampleFile) @@ -82,7 +82,7 @@ public class OleBlobTest extends TestCase } finally { ByteUtil.closeQuietly(blob); } - + try { blob = new OleBlob.Builder() .setPackagePrettyName("Text File") @@ -109,14 +109,14 @@ public class OleBlobTest extends TestCase assertEquals(sampleFilePath, spc.getFilePath()); assertEquals(sampleFilePath, spc.getLocalFilePath()); assertEquals(sampleFileName, spc.getFileName()); - assertEquals(OleBlob.Builder.PACKAGE_PRETTY_NAME, + assertEquals(OleBlob.Builder.PACKAGE_PRETTY_NAME, spc.getPrettyName()); - assertEquals(OleBlob.Builder.PACKAGE_TYPE_NAME, + assertEquals(OleBlob.Builder.PACKAGE_TYPE_NAME, spc.getTypeName()); - assertEquals(OleBlob.Builder.PACKAGE_TYPE_NAME, + assertEquals(OleBlob.Builder.PACKAGE_TYPE_NAME, spc.getClassName()); assertEquals(sampleFileBytes.length, spc.length()); - assertTrue(Arrays.equals(sampleFileBytes, + assertTrue(Arrays.equals(sampleFileBytes, toByteArray(spc.getStream(), spc.length()))); break; @@ -130,7 +130,7 @@ public class OleBlobTest extends TestCase assertEquals(OleBlob.Builder.PACKAGE_TYPE_NAME, lc.getTypeName()); assertEquals(OleBlob.Builder.PACKAGE_TYPE_NAME, lc.getClassName()); break; - + case 3: OleBlob.OtherContent oc = (OleBlob.OtherContent)content; assertEquals(OleBlob.ContentType.OTHER, oc.getType()); @@ -138,7 +138,7 @@ public class OleBlobTest extends TestCase assertEquals("Text.File", oc.getClassName()); assertEquals("TextFile", oc.getTypeName()); assertEquals(sampleFileBytes.length, oc.length()); - assertTrue(Arrays.equals(sampleFileBytes, + assertTrue(Arrays.equals(sampleFileBytes, toByteArray(oc.getStream(), oc.length()))); break; default: @@ -149,8 +149,8 @@ public class OleBlobTest extends TestCase } } - db.close(); - } + db.close(); + } } public void testReadBlob() throws Exception @@ -198,7 +198,7 @@ public class OleBlobTest extends TestCase } else { if("test_word.doc".equals(name)) { - checkCompoundEntries(cc, + checkCompoundEntries(cc, "/%02OlePres000", 466, "/WordDocument", 4096, "/%05SummaryInformation", 4096, @@ -210,7 +210,7 @@ public class OleBlobTest extends TestCase "/%01Ole", 20); checkCompoundStorage(cc, attach); } else if("test_excel.xls".equals(name)) { - checkCompoundEntries(cc, + checkCompoundEntries(cc, "/%02OlePres000", 1326, "/%03AccessObjSiteData", 56, "/%05SummaryInformation", 200, @@ -243,10 +243,10 @@ public class OleBlobTest extends TestCase } db.close(); - } + } } - private static void checkCompoundEntries(OleBlob.CompoundContent cc, + private static void checkCompoundEntries(OleBlob.CompoundContent cc, Object... entryInfo) throws Exception { @@ -262,7 +262,7 @@ public class OleBlobTest extends TestCase } } - private static void checkCompoundStorage(OleBlob.CompoundContent cc, + private static void checkCompoundStorage(OleBlob.CompoundContent cc, Attachment attach) throws Exception { @@ -273,7 +273,7 @@ public class OleBlobTest extends TestCase fout.write(attach.getFileData()); fout.close(); - NPOIFSFileSystem attachFs = new NPOIFSFileSystem(tmpData, true); + POIFSFileSystem attachFs = new POIFSFileSystem(tmpData, true); for(OleBlob.CompoundContent.Entry e : cc) { DocumentEntry attachE = null; @@ -284,7 +284,7 @@ public class OleBlobTest extends TestCase continue; } - byte[] attachEBytes = toByteArray(new DocumentInputStream(attachE), + byte[] attachEBytes = toByteArray(new DocumentInputStream(attachE), attachE.getSize()); byte[] entryBytes = toByteArray(e.getStream(), e.length()); @@ -292,9 +292,9 @@ public class OleBlobTest extends TestCase } ByteUtil.closeQuietly(attachFs); - + } finally { tmpData.delete(); - } + } } } diff --git a/src/test/resources/log4j_test.properties b/src/test/resources/log4j_test.properties deleted file mode 100644 index 6647228..0000000 --- a/src/test/resources/log4j_test.properties +++ /dev/null @@ -1,6 +0,0 @@ -log4j.rootCategory=INFO, stdout -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=**** %-5p %d{MMM d HH:mm:ss} [%F] - %m%n - -log4j.category.com.healthmarketscience.jackcess=ERROR diff --git a/src/test/resources/logging_test.properties b/src/test/resources/logging_test.properties new file mode 100644 index 0000000..1d5324b --- /dev/null +++ b/src/test/resources/logging_test.properties @@ -0,0 +1 @@ +com.healthmarketscience.jackcess.level=SEVERE |