aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/Cursor.java19
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/CursorBuilder.java4
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/DataType.java8
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/Database.java61
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/DatabaseBuilder.java53
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/DateTimeType.java33
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/IndexCursor.java16
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java4
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/PropertyMap.java12
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/RelationshipBuilder.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/Row.java21
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/Table.java15
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/TableBuilder.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/complex/Attachment.java28
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java46
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/complex/Version.java10
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/expr/Identifier.java9
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java15
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java8
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java55
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/expr/Value.java6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/expr/package-info.java20
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java39
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java462
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/CompoundOleUtil.java27
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java46
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/CustomToStringStyle.java20
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java34
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java211
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/DateTimeContext.java34
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/DefaultCodecProvider.java9
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java355
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java14
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/IndexPageCache.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java35
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java3
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java21
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java13
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/RowIdImpl.java3
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/RowImpl.java26
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java40
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java53
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexColumnInfoImpl.java21
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java123
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnInfoImpl.java4
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnPropertyMap.java16
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java5
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java75
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java16
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java16
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java14
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java449
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java16
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java209
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java33
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java1505
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java11
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/NumberFormatter.java10
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java7
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java51
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/AppendQueryImpl.java5
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/BaseSelectQueryImpl.java4
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/CrossTabQueryImpl.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/DataDefinitionQueryImpl.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/MakeTableQueryImpl.java3
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/PassthroughQueryImpl.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/QueryFormat.java15
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/QueryImpl.java10
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/UnionQueryImpl.java3
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/query/UpdateQueryImpl.java4
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/CaseInsensitiveColumnMatcher.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java45
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/EntryIterableBuilder.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/ErrorHandler.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/IterableBuilder.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/LinkResolver.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java95
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/OleBlob.java12
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/ReplacementErrorHandler.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/RowFilter.java36
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java9
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java1
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/SimpleExportFilter.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/SimpleImportFilter.java2
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/util/TableIterableBuilder.java1
98 files changed, 3558 insertions, 1211 deletions
diff --git a/src/main/java/com/healthmarketscience/jackcess/Cursor.java b/src/main/java/com/healthmarketscience/jackcess/Cursor.java
index 7260b79..2d0c9f7 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/>
+ * <p>
* 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,11 +135,12 @@ 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/>
+ * <p>
* 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
*/
+ @Override
public Iterator<Row> iterator();
/**
@@ -151,7 +152,7 @@ public interface Cursor extends Iterable<Row>
/**
* Delete the current row.
- * <p/>
+ * <p>
* 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 +179,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 -&gt; Column value), or
* {@code null} if no next row is found
*/
public Row getNextRow() throws IOException;
@@ -186,7 +187,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 -&gt; Column value), or
* {@code null} if no next row is found
*/
public Row getNextRow(Collection<String> columnNames)
@@ -194,7 +195,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 -&gt; Column value), or
* {@code null} if no previous row is found
*/
public Row getPreviousRow() throws IOException;
@@ -202,7 +203,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 -&gt; Column value), or
* {@code null} if no previous row is found
*/
public Row getPreviousRow(Collection<String> columnNames)
@@ -325,12 +326,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 -&gt; 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 -&gt; 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..d4f063c 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/>
+ * <p>
* Simple example traversal:
* <pre>
* for(Row row : table.newCursor().toCursor()) {
* // ... process each row ...
* }
* </pre>
- * <p/>
+ * <p>
* 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 380f7f1..6070dbd 100644
--- a/src/main/java/com/healthmarketscience/jackcess/DataType.java
+++ b/src/main/java/com/healthmarketscience/jackcess/DataType.java
@@ -24,6 +24,7 @@ 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;
@@ -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),
/**
diff --git a/src/main/java/com/healthmarketscience/jackcess/Database.java b/src/main/java/com/healthmarketscience/jackcess/Database.java
index d853fe8..4fa8741 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/>
+ * <p>
* 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/>
+ * <p>
* 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
@@ -104,7 +106,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
"com.healthmarketscience.jackcess.brokenNio";
/** system property which can be used to set the default sort order for
- * table columns. Value should be one {@link Table.ColumnOrder} enum
+ * table columns. Value should be one of {@link Table.ColumnOrder} enum
* values.
* @usage _intermediate_field_
*/
@@ -132,6 +134,13 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
public static final String ENABLE_EXPRESSION_EVALUATION_PROPERTY =
"com.healthmarketscience.jackcess.enableExpressionEvaluation";
+ /** system property which can be used to set the default date/Time type.
+ * Value should be one of {@link DateTimeType} enum values.
+ * @usage _general_field_
+ */
+ public static final String DATE_TIME_TYPE_PROPERTY =
+ "com.healthmarketscience.jackcess.dateTimeType";
+
/**
* Enum which indicates which version of Access created the database.
* @usage _general_class_
@@ -179,6 +188,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 +215,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 +255,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
/**
* Finds all the relationships in the database in <i>non-system</i> tables.
- * </p>
+ * <p>
* 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 +265,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
/**
* Finds <i>all</i> the relationships in the database, <i>including system
* tables</i>.
- * </p>
+ * <p>
* 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 +334,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
* databases) to disk.
* @usage _general_method_
*/
+ @Override
public void flush() throws IOException;
/**
@@ -329,6 +345,7 @@ public interface Database extends Iterable<Table>, Closeable, Flushable
* OutputStream or jdbc Connection).
* @usage _general_method_
*/
+ @Override
public void close() throws IOException;
/**
@@ -377,18 +394,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 +525,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..da9681e 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/>
+ * <p>
* Simple example usage:
* <pre>
* Database db = DatabaseBuilder.open(new File("test.mdb"));
* </pre>
- * <p/>
+ * <p>
* 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 &gt;= 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..7ff920b 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/>
+ * <p>
* 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/>
+ * <p>
* 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/>
+ * <p>
* 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/>
+ * <p>
* Note, this change will not be persisted until the {@link
* PropertyMap#save} method has been called.
*/
@@ -204,6 +204,7 @@ public interface PropertyMap extends Iterable<PropertyMap.Property>
_value = (short)value;
}
+ @Override
public Short getValue() {
return _value;
}
@@ -228,6 +229,7 @@ public interface PropertyMap extends Iterable<PropertyMap.Property>
_value = (byte)value;
}
+ @Override
public Byte getValue() {
return _value;
}
@@ -261,6 +263,7 @@ public interface PropertyMap extends Iterable<PropertyMap.Property>
_value = (byte)value;
}
+ @Override
public Byte getValue() {
return _value;
}
@@ -287,6 +290,7 @@ public interface PropertyMap extends Iterable<PropertyMap.Property>
_value = (byte)value;
}
+ @Override
public Byte getValue() {
return _value;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/RelationshipBuilder.java b/src/main/java/com/healthmarketscience/jackcess/RelationshipBuilder.java
index 3ca3e85..f9ae6e9 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/>
+ * <p>
* 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..430dfd1 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-&gt;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>
+ * <p>
* 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..c1d1ddd 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/>
+ * <p>
* 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 -&gt; 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 -&gt; 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/>
+ * <p>
* 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/>
+ * <p>
* Note, if this table has an auto-number column, the values generated will
* be put back into the appropriate row maps.
* <p>
@@ -278,13 +278,14 @@ 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/>
+ * <p>
* For more advanced iteration, use the {@link #getDefaultCursor default
* cursor} directly.
* @throws RuntimeIOException if an IOException is thrown by one of the
* operations, the actual exception will be contained within
* @usage _general_method_
*/
+ @Override
public Iterator<Row> iterator();
/**
@@ -296,7 +297,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 -&gt; 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..b26da3e 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/>
+ * <p>
* 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/NumericConfig.java b/src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java
index 74dd06e..d231522 100644
--- a/src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java
+++ b/src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java
@@ -35,7 +35,7 @@ public class NumericConfig
2, true, false, true, 3, Locale.US);
public enum Type {
- CURRENCY, FIXED, STANDARD, PERCENT, SCIENTIFIC;
+ CURRENCY, FIXED, STANDARD, PERCENT, SCIENTIFIC, EURO;
}
private final int _numDecDigits;
@@ -49,6 +49,7 @@ public class NumericConfig
private final String _standardFormat;
private final String _percentFormat;
private final String _scientificFormat;
+ private final String _euroFormat;
public NumericConfig(int numDecDigits, boolean incLeadingDigit,
boolean useNegParens, boolean useNegCurrencyParens,
@@ -75,6 +76,9 @@ public class NumericConfig
_scientificFormat = FormatUtil.createNumberFormatPattern(
FormatUtil.NumPatternType.SCIENTIFIC, _numDecDigits, true,
false, 0);
+ _euroFormat = FormatUtil.createNumberFormatPattern(
+ FormatUtil.NumPatternType.EURO, _numDecDigits, _incLeadingDigit,
+ _useNegCurrencyParens, _numGroupDigits);
}
public int getNumDecimalDigits() {
@@ -109,6 +113,8 @@ public class NumericConfig
return _percentFormat;
case SCIENTIFIC:
return _scientificFormat;
+ case EURO:
+ return _euroFormat;
default:
throw new IllegalArgumentException("unknown number type " + type);
}
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. "&lt;date&gt; &lt;time&gt;".
*
* @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..d5d380a 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/>
+ * <p>
* 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/>
+ * <p>
* 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/>
+ *
* <h2>Supporting Classes</h2>
- * <p/>
+ * <p>
* The classes in this package make up the public api for expression handling
* in Jackcess. They generally fall into two categories:
- * <p/>
+ *
* <h3>General Use Classes</h3>
- * <p/>
+ *
* <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/>
+ *
* <h3>Advanced Use Classes</h3>
- * <p/>
+ *
* <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/>
+ *
* <h2>Function Support</h2>
- * <p/>
+ * <p>
* 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..36b32d8 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;
@@ -78,50 +80,62 @@ public abstract class BaseEvalContext implements EvalContext
return _dbCtx.getDatabase();
}
+ @Override
public TemporalConfig getTemporalConfig() {
return _dbCtx.getTemporalConfig();
}
- public SimpleDateFormat createDateFormat(String formatStr) {
- return _dbCtx.createDateFormat(formatStr);
+ @Override
+ public DateTimeFormatter createDateFormatter(String formatStr) {
+ return _dbCtx.createDateFormatter(formatStr);
}
- public Calendar getCalendar() {
- return _dbCtx.getCalendar();
+ @Override
+ public ZoneId getZoneId() {
+ return _dbCtx.getZoneId();
}
+ @Override
public NumericConfig getNumericConfig() {
return _dbCtx.getNumericConfig();
}
+ @Override
public DecimalFormat createDecimalFormat(String formatStr) {
return _dbCtx.createDecimalFormat(formatStr);
}
+ @Override
public float getRandom(Integer seed) {
return _dbCtx.getRandom(seed);
}
+ @Override
public Value.Type getResultType() {
return null;
}
+ @Override
public Value getThisColumnValue() {
throw new UnsupportedOperationException();
}
+ @Override
public Value getIdentifierValue(Identifier identifier) {
throw new UnsupportedOperationException();
}
+ @Override
public Bindings getBindings() {
return _dbCtx.getBindings();
}
+ @Override
public Object get(String key) {
return _dbCtx.getBindings().get(key);
}
+ @Override
public void put(String key, Object value) {
_dbCtx.getBindings().put(key, value);
}
@@ -146,7 +160,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 +175,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());
@@ -203,26 +220,32 @@ public abstract class BaseEvalContext implements EvalContext
return expr;
}
+ @Override
public Object eval(EvalContext ctx) {
return getExpr().eval(ctx);
}
+ @Override
public String toDebugString(LocaleContext ctx) {
return getExpr().toDebugString(ctx);
}
+ @Override
public String toRawString() {
return _exprStr;
}
+ @Override
public String toCleanString(LocaleContext ctx) {
return getExpr().toCleanString(ctx);
}
+ @Override
public boolean isConstant() {
return getExpr().isConstant();
}
+ @Override
public void collectIdentifiers(Collection<Identifier> identifiers) {
getExpr().collectIdentifiers(identifiers);
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java
index 9aa7d17..8e3802b 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/>
+ * <p>
* 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 3445ba3..212b912 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>, DateTimeContext
+{
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_
@@ -336,10 +363,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
// base does nothing
}
+ @Override
public TableImpl getTable() {
return _table;
}
+ @Override
public DatabaseImpl getDatabase() {
return getTable().getDatabase();
}
@@ -358,14 +387,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;
}
@@ -377,6 +409,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return _columnNumber;
}
+ @Override
public int getColumnIndex() {
return _columnIndex;
}
@@ -395,22 +428,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();
}
@@ -429,10 +467,12 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return 0;
}
+ @Override
public short getLength() {
return _columnLength;
}
+ @Override
public final short getLengthInUnits() {
if(_lengthInUnits == INVALID_LENGTH) {
_lengthInUnits = calcLengthInUnits();
@@ -444,6 +484,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return getType().toUnitSize(getLength(), getFormat());
}
+ @Override
public boolean isCalculated() {
return _calculated;
}
@@ -466,14 +507,27 @@ 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();
+ }
+
+ @Override
+ public DateTimeFactory getDateTimeFactory() {
+ return getDatabase().getDateTimeFactory();
}
+ @Override
public boolean isAppendOnly() {
return (getVersionHistoryColumn() != null);
}
+ @Override
public ColumnImpl getVersionHistoryColumn() {
return null;
}
@@ -493,10 +547,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;
}
@@ -593,12 +649,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()) {
@@ -659,6 +717,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return _autoNumberGenerator;
}
+ @Override
public PropertyMap getProperties() throws IOException {
if(_props == null) {
_props = getTable().getPropertyMaps().get(getName());
@@ -666,20 +725,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);
}
@@ -898,45 +961,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
@@ -944,29 +987,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));
}
}
@@ -977,36 +1040,87 @@ 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)
- {
- return toDateDouble(value, db.getCalendar());
+ private static double toDateDouble(Object value, DateTimeContext dtc) {
+ return dtc.getDateTimeFactory().toDateDouble(value, dtc);
}
- /**
- * 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);
- return toLocalDateDouble(time);
+ private static LocalDateTime toLocalDateTime(
+ TemporalAccessor value, DateTimeContext dtc) {
+
+ // 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(
+ dtc.getZoneId()).toLocalDateTime();
+ }
+ if(value instanceof Instant) {
+ return LocalDateTime.ofInstant((Instant)value, dtc.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 = dtc.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)
- {
+ private static Instant toInstant(TemporalAccessor value, DateTimeContext dtc) {
+ if(value instanceof ZonedDateTime) {
+ return ((ZonedDateTime)value).toInstant();
+ }
+ if(value instanceof Instant) {
+ return (Instant)value;
+ }
+ return toLocalDateTime(value, dtc).atZone(dtc.getZoneId()).toInstant();
+ }
+
+ static double toLocalDateDouble(long time) {
time += MILLIS_BETWEEN_EPOCH_AND_1900;
if(time < 0L) {
@@ -1020,11 +1134,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) ?
@@ -1036,24 +1202,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());
}
/**
@@ -1563,6 +1724,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;
@@ -1618,6 +1780,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());
}
@@ -1648,6 +1812,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());
}
@@ -1983,6 +2149,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;
}
@@ -2007,8 +2181,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:
@@ -2028,6 +2204,11 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
}
}
+ protected 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());
}
@@ -2045,8 +2226,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;
@@ -2063,6 +2246,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)
@@ -2467,4 +2685,96 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
sb.append("allowZeroLength=false");
}
}
+
+ /**
+ * Factory which handles date/time values appropriately for a DateTimeType.
+ */
+ protected static abstract class DateTimeFactory
+ {
+ public abstract DateTimeType getType();
+
+ public abstract Object fromDateBits(ColumnImpl col, long dateBits);
+
+ public abstract double toDateDouble(Object value, DateTimeContext dtc);
+
+ public abstract Object toInternalValue(DatabaseImpl db, Object value);
+ }
+
+ /**
+ * Factory impl for legacy Date handling.
+ */
+ private 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 double toDateDouble(Object value, DateTimeContext dtc) {
+ // ZoneId and TimeZone have different rules for older timezones, so we
+ // need to consistently use one or the other depending on the date/time
+ // type
+ long time = 0L;
+ if(value instanceof TemporalAccessor) {
+ time = toInstant((TemporalAccessor)value, dtc).toEpochMilli();
+ } else {
+ time = toDateLong(value);
+ }
+ // seems access stores dates in the local timezone. guess you just
+ // hope you read it in the same timezone in which it was written!
+ time += getToLocalTimeZoneOffset(time, dtc.getTimeZone());
+ return toLocalDateDouble(time);
+ }
+
+ @Override
+ public Object toInternalValue(DatabaseImpl db, Object value) {
+ return ((value instanceof Date) ? value :
+ new Date(toDateLong(value)));
+ }
+ }
+
+ /**
+ * Factory impl for LocalDateTime handling.
+ */
+ private 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 double toDateDouble(Object value, DateTimeContext dtc) {
+ // ZoneId and TimeZone have different rules for older timezones, so we
+ // need to consistently use one or the other depending on the date/time
+ // type
+ if(!(value instanceof TemporalAccessor)) {
+ value = Instant.ofEpochMilli(toDateLong(value));
+ }
+ return ColumnImpl.toDateDouble(
+ toLocalDateTime((TemporalAccessor)value, dtc));
+ }
+
+ @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..897a195 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/>
+ * <p>
* 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()
@@ -67,6 +67,7 @@ public class CompoundOleUtil implements CompoundPackageFactory
/**
* Creates a nes CompoundContent for the given blob information.
*/
+ @Override
public ContentImpl createCompoundPackageContent(
OleBlobImpl blob, String prettyName, String className, String typeName,
ByteBuffer blobBb, int dataBlockLen)
@@ -139,7 +140,7 @@ public class CompoundOleUtil implements CompoundPackageFactory
extends EmbeddedPackageContentImpl
implements CompoundContent
{
- private NPOIFSFileSystem _fs;
+ private POIFSFileSystem _fs;
private CompoundContentImpl(
OleBlobImpl blob, String prettyName, String className,
@@ -148,17 +149,19 @@ public class CompoundOleUtil implements CompoundPackageFactory
super(blob, prettyName, className, typeName, position, length);
}
+ @Override
public ContentType getType() {
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;
}
+ @Override
public Iterator<Entry> iterator() {
try {
return getEntries(new ArrayList<Entry>(), getFileSystem().getRoot(),
@@ -168,15 +171,18 @@ public class CompoundOleUtil implements CompoundPackageFactory
}
}
+ @Override
public EntryImpl getEntry(String entryName) throws IOException {
return new EntryImpl(entryName,
getDocumentEntry(entryName, getFileSystem().getRoot()));
}
+ @Override
public boolean hasContentsEntry() throws IOException {
return getFileSystem().getRoot().hasEntry(CONTENTS_ENTRY);
}
+ @Override
public EntryImpl getContentsEntry() throws IOException {
return getEntry(CONTENTS_ENTRY);
}
@@ -230,30 +236,37 @@ public class CompoundOleUtil implements CompoundPackageFactory
_docEntry = docEntry;
}
+ @Override
public ContentType getType() {
return ContentType.UNKNOWN;
}
+ @Override
public String getName() {
return _name;
}
+ @Override
public CompoundContentImpl getParent() {
return CompoundContentImpl.this;
}
+ @Override
public OleBlobImpl getBlob() {
return getParent().getBlob();
}
+ @Override
public long length() {
return _docEntry.getSize();
}
+ @Override
public InputStream getStream() throws IOException {
return new DocumentInputStream(_docEntry);
}
+ @Override
public void writeTo(OutputStream out) throws IOException {
InputStream in = null;
try {
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java
index 85a53dd..7b973a4 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java
@@ -102,10 +102,12 @@ public abstract class CursorImpl implements Cursor
return _rowState;
}
+ @Override
public IdImpl getId() {
return _id;
}
+ @Override
public TableImpl getTable() {
return _table;
}
@@ -118,18 +120,22 @@ public abstract class CursorImpl implements Cursor
return getTable().getPageChannel();
}
+ @Override
public ErrorHandler getErrorHandler() {
return _rowState.getErrorHandler();
}
+ @Override
public void setErrorHandler(ErrorHandler newErrorHandler) {
_rowState.setErrorHandler(newErrorHandler);
}
+ @Override
public ColumnMatcher getColumnMatcher() {
return _columnMatcher;
}
+ @Override
public void setColumnMatcher(ColumnMatcher columnMatcher) {
if(columnMatcher == null) {
columnMatcher = getDefaultColumnMatcher();
@@ -144,10 +150,12 @@ public abstract class CursorImpl implements Cursor
return SimpleColumnMatcher.INSTANCE;
}
+ @Override
public SavepointImpl getSavepoint() {
return new SavepointImpl(_id, _curPos, _prevPos);
}
+ @Override
public void restoreSavepoint(Savepoint savepoint)
throws IOException
{
@@ -180,22 +188,27 @@ public abstract class CursorImpl implements Cursor
return _lastPos;
}
+ @Override
public void reset() {
beforeFirst();
}
+ @Override
public void beforeFirst() {
reset(MOVE_FORWARD);
}
+ @Override
public void afterLast() {
reset(MOVE_REVERSE);
}
+ @Override
public boolean isBeforeFirst() throws IOException {
return isAtBeginning(MOVE_FORWARD);
}
+ @Override
public boolean isAfterLast() throws IOException {
return isAtBeginning(MOVE_REVERSE);
}
@@ -207,6 +220,7 @@ public abstract class CursorImpl implements Cursor
return false;
}
+ @Override
public boolean isCurrentRowDeleted() throws IOException
{
// we need to ensure that the "deleted" flag has been read for this row
@@ -224,10 +238,12 @@ public abstract class CursorImpl implements Cursor
_rowState.reset();
}
+ @Override
public Iterator<Row> iterator() {
return new RowIterator(null, true, MOVE_FORWARD);
}
+ @Override
public IterableBuilder newIterable() {
return new IterableBuilder(this);
}
@@ -260,34 +276,41 @@ public abstract class CursorImpl implements Cursor
}
}
+ @Override
public void deleteCurrentRow() throws IOException {
_table.deleteRow(_rowState, _curPos.getRowId());
}
+ @Override
public Object[] updateCurrentRow(Object... row) throws IOException {
return _table.updateRow(_rowState, _curPos.getRowId(), row);
}
+ @Override
public <M extends Map<String,Object>> M updateCurrentRowFromMap(M row)
throws IOException
{
return _table.updateRowFromMap(_rowState, _curPos.getRowId(), row);
}
+ @Override
public Row getNextRow() throws IOException {
return getNextRow(null);
}
+ @Override
public Row getNextRow(Collection<String> columnNames)
throws IOException
{
return getAnotherRow(columnNames, MOVE_FORWARD);
}
+ @Override
public Row getPreviousRow() throws IOException {
return getPreviousRow(null);
}
+ @Override
public Row getPreviousRow(Collection<String> columnNames)
throws IOException
{
@@ -299,7 +322,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 -&gt; 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.
*/
@@ -313,11 +336,13 @@ public abstract class CursorImpl implements Cursor
return null;
}
+ @Override
public boolean moveToNextRow() throws IOException
{
return moveToAnotherRow(MOVE_FORWARD);
}
+ @Override
public boolean moveToPreviousRow() throws IOException
{
return moveToAnotherRow(MOVE_REVERSE);
@@ -407,6 +432,7 @@ public abstract class CursorImpl implements Cursor
return(!_curPos.equals(getDirHandler(moveForward).getEndPosition()));
}
+ @Override
public boolean findRow(RowId rowId) throws IOException
{
RowIdImpl rowIdImpl = (RowIdImpl)rowId;
@@ -435,6 +461,7 @@ public abstract class CursorImpl implements Cursor
}
}
+ @Override
public boolean findFirstRow(Column columnPattern, Object valuePattern)
throws IOException
{
@@ -449,6 +476,7 @@ public abstract class CursorImpl implements Cursor
prepareSearchInfo(columnPattern, valuePattern));
}
+ @Override
public boolean findNextRow(Column columnPattern, Object valuePattern)
throws IOException
{
@@ -489,12 +517,14 @@ public abstract class CursorImpl implements Cursor
}
}
+ @Override
public boolean findFirstRow(Map<String,?> rowPattern) throws IOException
{
return findAnotherRow(rowPattern, true, MOVE_FORWARD, _columnMatcher,
prepareSearchInfo(rowPattern));
}
+ @Override
public boolean findNextRow(Map<String,?> rowPattern)
throws IOException
{
@@ -528,6 +558,7 @@ public abstract class CursorImpl implements Cursor
}
}
+ @Override
public boolean currentRowMatches(Column columnPattern, Object valuePattern)
throws IOException
{
@@ -550,6 +581,7 @@ public abstract class CursorImpl implements Cursor
getCurrentRowValue(columnPattern));
}
+ @Override
public boolean currentRowMatches(Map<String,?> rowPattern)
throws IOException
{
@@ -663,11 +695,13 @@ public abstract class CursorImpl implements Cursor
return true;
}
+ @Override
public int moveNextRows(int numRows) throws IOException
{
return moveSomeRows(numRows, MOVE_FORWARD);
}
+ @Override
public int movePreviousRows(int numRows) throws IOException
{
return moveSomeRows(numRows, MOVE_REVERSE);
@@ -688,17 +722,20 @@ public abstract class CursorImpl implements Cursor
return numMovedRows;
}
+ @Override
public Row getCurrentRow() throws IOException
{
return getCurrentRow(null);
}
+ @Override
public Row getCurrentRow(Collection<String> columnNames)
throws IOException
{
return _table.getRow(_rowState, _curPos.getRowId(), columnNames);
}
+ @Override
public Object getCurrentRowValue(Column column)
throws IOException
{
@@ -711,6 +748,7 @@ public abstract class CursorImpl implements Cursor
return _table.getRowValue(_rowState, _curPos.getRowId(), column);
}
+ @Override
public void setCurrentRowValue(Column column, Object value)
throws IOException
{
@@ -802,6 +840,7 @@ public abstract class CursorImpl implements Cursor
}
}
+ @Override
public boolean hasNext() {
if(_hasNext == null) {
try {
@@ -814,6 +853,7 @@ public abstract class CursorImpl implements Cursor
return _hasNext;
}
+ @Override
public Row next() {
if(!hasNext()) {
throw new NoSuchElementException();
@@ -827,6 +867,7 @@ public abstract class CursorImpl implements Cursor
}
}
+ @Override
public void remove() {
if(_validRow) {
try {
@@ -983,6 +1024,7 @@ public abstract class CursorImpl implements Cursor
/**
* Returns the unique RowId of the position of the cursor.
*/
+ @Override
public abstract RowIdImpl getRowId();
/**
@@ -1010,10 +1052,12 @@ public abstract class CursorImpl implements Cursor
_prevPos = prevPos;
}
+ @Override
public IdImpl getCursorId() {
return _cursorId;
}
+ @Override
public PositionImpl getCurrentPosition() {
return _curPos;
}
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..227cdd8 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;
@@ -57,10 +57,12 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
return _db;
}
+ @Override
public TemporalConfig getTemporalConfig() {
return _temporal;
}
+ @Override
public void setTemporalConfig(TemporalConfig temporal) {
if(_temporal != temporal) {
_temporal = temporal;
@@ -68,14 +70,17 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
}
}
- public Calendar getCalendar() {
- return _db.getCalendar();
+ @Override
+ public ZoneId getZoneId() {
+ return _db.getZoneId();
}
+ @Override
public NumericConfig getNumericConfig() {
return _numeric;
}
+ @Override
public void setNumericConfig(NumericConfig numeric) {
if(_numeric != numeric) {
_numeric = numeric;
@@ -83,35 +88,40 @@ public class DBEvalContext implements Expressionator.ParseContext, EvalConfig
}
}
+ @Override
public FunctionLookup getFunctionLookup() {
return _funcs;
}
+ @Override
public void setFunctionLookup(FunctionLookup lookup) {
_funcs = lookup;
}
+ @Override
public Bindings getBindings() {
return _bindings;
}
+ @Override
public void setBindings(Bindings bindings) {
_bindings = bindings;
}
- public SimpleDateFormat createDateFormat(String formatStr) {
+ @Override
+ 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;
}
+ @Override
public DecimalFormat createDecimalFormat(String formatStr) {
if(_dfs == null) {
_dfs = new SimpleCache<String,DecimalFormat>(MAX_CACHE_SIZE);
@@ -128,8 +138,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..1db1c3b 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,10 +27,14 @@ 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;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -53,6 +56,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 +77,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 +88,7 @@ import org.apache.commons.logging.LogFactory;
* @author Tim McCune
* @usage _intermediate_class_
*/
-public class DatabaseImpl implements Database
+public class DatabaseImpl implements Database, DateTimeContext
{
private static final Log LOG = LogFactory.getLog(DatabaseImpl.class);
@@ -198,9 +202,15 @@ 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";
- /** read/write channel access mode */
- public static final String RW_CHANNEL_MODE = "rw";
+ public static final OpenOption[] RO_CHANNEL_OPTS =
+ {StandardOpenOption.READ};
+ /** read/write channel access mode for existing files */
+ public static final OpenOption[] RW_CHANNEL_OPTS =
+ {StandardOpenOption.READ, StandardOpenOption.WRITE};
+ /** read/write/create channel access mode for new files */
+ public static final OpenOption[] RWC_CHANNEL_OPTS =
+ {StandardOpenOption.READ, StandardOpenOption.WRITE,
+ StandardOpenOption.CREATE};
/** Name of the system object that is the parent of all tables */
private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables";
@@ -252,7 +262,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 +310,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 +347,10 @@ 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;
/**
* Open an existing Database. If the existing file is not writeable or the
@@ -364,23 +376,23 @@ 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);
+ channel = openChannel(mdbFile, readOnly, false);
closeChannel = true;
}
@@ -434,7 +446,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
@@ -451,7 +463,7 @@ public class DatabaseImpl implements Database
boolean closeChannel = false;
if(channel == null) {
- channel = openChannel(mdbFile, false);
+ channel = openChannel(mdbFile, false, true);
closeChannel = true;
}
@@ -486,11 +498,13 @@ 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, boolean create)
+ throws IOException
{
- final String mode = (readOnly ? RO_CHANNEL_MODE : RW_CHANNEL_MODE);
- return new RandomAccessFile(mdbFile, mode).getChannel();
+ OpenOption[] opts = (readOnly ? RO_CHANNEL_OPTS :
+ (create ? RWC_CHANNEL_OPTS : RW_CHANNEL_OPTS));
+ return FileChannel.open(mdbFile, opts);
}
/**
@@ -512,7 +526,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)
@@ -528,8 +542,9 @@ public class DatabaseImpl implements Database
_allowAutoNumInsert = getDefaultAllowAutoNumberInsert();
_evaluateExpressions = getDefaultEvaluateExpressions();
_fileFormat = fileFormat;
+ setZoneInfo(timeZone, null);
+ _dtf = ColumnImpl.getDateTimeFactory(getDefaultDateTimeType());
_pageChannel = new PageChannel(channel, closeChannel, _format, autoSync);
- _timeZone = ((timeZone == null) ? getDefaultTimeZone() : timeZone);
if(provider == null) {
provider = DefaultCodecProvider.INSTANCE;
}
@@ -541,7 +556,13 @@ public class DatabaseImpl implements Database
readSystemCatalog();
}
+ @Override
public File getFile() {
+ return ((_file != null) ? _file.toFile() : null);
+ }
+
+ @Override
+ public Path getPath() {
return _file;
}
@@ -597,27 +618,33 @@ public class DatabaseImpl implements Database
return _complexCols;
}
+ @Override
public ErrorHandler getErrorHandler() {
return((_dbErrorHandler != null) ? _dbErrorHandler : ErrorHandler.DEFAULT);
}
+ @Override
public void setErrorHandler(ErrorHandler newErrorHandler) {
_dbErrorHandler = newErrorHandler;
}
+ @Override
public LinkResolver getLinkResolver() {
return((_linkResolver != null) ? _linkResolver : LinkResolver.DEFAULT);
}
+ @Override
public void setLinkResolver(LinkResolver newLinkResolver) {
_linkResolver = newLinkResolver;
}
+ @Override
public Map<String,Database> getLinkedDatabases() {
return ((_linkedDbs == null) ? Collections.<String,Database>emptyMap() :
Collections.unmodifiableMap(_linkedDbs));
}
+ @Override
public boolean isLinkedTable(Table table) throws IOException {
if((table == null) || (this == table.getDatabase())) {
@@ -645,27 +672,62 @@ 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;
+ }
+
+ @Override
+ 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);
+ }
+
+ @Override
+ public ColumnImpl.DateTimeFactory getDateTimeFactory() {
+ return _dtf;
+ }
+
+ @Override
public Charset getCharset()
{
return _charset;
}
+ @Override
public void setCharset(Charset newCharset) {
if(newCharset == null) {
newCharset = getDefaultCharset(getFormat());
@@ -673,10 +735,12 @@ public class DatabaseImpl implements Database
_charset = newCharset;
}
+ @Override
public Table.ColumnOrder getColumnOrder() {
return _columnOrder;
}
+ @Override
public void setColumnOrder(Table.ColumnOrder newColumnOrder) {
if(newColumnOrder == null) {
newColumnOrder = getDefaultColumnOrder();
@@ -684,10 +748,12 @@ public class DatabaseImpl implements Database
_columnOrder = newColumnOrder;
}
+ @Override
public boolean isEnforceForeignKeys() {
return _enforceForeignKeys;
}
+ @Override
public void setEnforceForeignKeys(Boolean newEnforceForeignKeys) {
if(newEnforceForeignKeys == null) {
newEnforceForeignKeys = getDefaultEnforceForeignKeys();
@@ -695,10 +761,12 @@ public class DatabaseImpl implements Database
_enforceForeignKeys = newEnforceForeignKeys;
}
+ @Override
public boolean isAllowAutoNumberInsert() {
return _allowAutoNumInsert;
}
+ @Override
public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
if(allowAutoNumInsert == null) {
allowAutoNumInsert = getDefaultAllowAutoNumberInsert();
@@ -706,10 +774,12 @@ public class DatabaseImpl implements Database
_allowAutoNumInsert = allowAutoNumInsert;
}
+ @Override
public boolean isEvaluateExpressions() {
return _evaluateExpressions;
}
+ @Override
public void setEvaluateExpressions(Boolean evaluateExpressions) {
if(evaluateExpressions == null) {
evaluateExpressions = getDefaultEvaluateExpressions();
@@ -717,10 +787,12 @@ public class DatabaseImpl implements Database
_evaluateExpressions = evaluateExpressions;
}
+ @Override
public ColumnValidatorFactory getColumnValidatorFactory() {
return _validatorFactory;
}
+ @Override
public void setColumnValidatorFactory(ColumnValidatorFactory newFactory) {
if(newFactory == null) {
newFactory = SimpleColumnValidatorFactory.INSTANCE;
@@ -735,17 +807,7 @@ public class DatabaseImpl implements Database
return _fkEnforcerSharedState;
}
- /**
- * @usage _advanced_method_
- */
- Calendar getCalendar() {
- if(_calendar == null) {
- _calendar = DatabaseBuilder.toCompatibleCalendar(
- Calendar.getInstance(_timeZone));
- }
- return _calendar;
- }
-
+ @Override
public EvalConfig getEvalConfig() {
return getEvalContext();
}
@@ -783,6 +845,7 @@ public class DatabaseImpl implements Database
return _propsHandler;
}
+ @Override
public FileFormat getFileFormat() throws IOException {
if(_fileFormat == null) {
@@ -940,6 +1003,7 @@ public class DatabaseImpl implements Database
}
}
+ @Override
public Set<String> getTableNames() throws IOException {
if(_tableNames == null) {
_tableNames = getTableNames(true, false, true);
@@ -947,6 +1011,7 @@ public class DatabaseImpl implements Database
return _tableNames;
}
+ @Override
public Set<String> getSystemTableNames() throws IOException {
return getTableNames(false, true, false);
}
@@ -961,6 +1026,7 @@ public class DatabaseImpl implements Database
return tableNames;
}
+ @Override
public Iterator<Table> iterator() {
try {
return new TableIterator(getTableNames());
@@ -979,14 +1045,17 @@ public class DatabaseImpl implements Database
}
}
+ @Override
public TableIterableBuilder newIterable() {
return new TableIterableBuilder(this);
}
+ @Override
public TableImpl getTable(String name) throws IOException {
return getTable(name, false);
}
+ @Override
public TableMetaData getTableMetaData(String name) throws IOException {
return getTableInfo(name, true);
}
@@ -1101,6 +1170,7 @@ public class DatabaseImpl implements Database
.toTable(this);
}
+ @Override
public void createLinkedTable(String name, String linkedDbName,
String linkedTableName)
throws IOException
@@ -1147,6 +1217,7 @@ public class DatabaseImpl implements Database
addToAccessControlEntries(tdefPageNumber, _tableParentId, _newTableSIDs);
}
+ @Override
public List<Relationship> getRelationships(Table table1, Table table2)
throws IOException
{
@@ -1174,6 +1245,7 @@ public class DatabaseImpl implements Database
return getRelationshipsImpl(table1, table2, true);
}
+ @Override
public List<Relationship> getRelationships(Table table)
throws IOException
{
@@ -1185,12 +1257,14 @@ public class DatabaseImpl implements Database
return getRelationshipsImpl((TableImpl)table, null, true);
}
+ @Override
public List<Relationship> getRelationships()
throws IOException
{
return getRelationshipsImpl(null, null, false);
}
+ @Override
public List<Relationship> getSystemRelationships()
throws IOException
{
@@ -1333,6 +1407,7 @@ public class DatabaseImpl implements Database
return ((i == 0) ? origName : (origName + i));
}
+ @Override
public List<Query> getQueries() throws IOException
{
// the queries table does not get loaded until first accessed
@@ -1382,6 +1457,7 @@ public class DatabaseImpl implements Database
return queries;
}
+ @Override
public TableImpl getSystemTable(String tableName) throws IOException
{
return getTable(tableName, true);
@@ -1397,6 +1473,7 @@ public class DatabaseImpl implements Database
return table;
}
+ @Override
public PropertyMap getDatabaseProperties() throws IOException {
if(_dbPropMaps == null) {
_dbPropMaps = getPropertiesForDbObject(OBJECT_NAME_DB_PROPS);
@@ -1404,6 +1481,7 @@ public class DatabaseImpl implements Database
return _dbPropMaps.getDefault();
}
+ @Override
public PropertyMap getSummaryProperties() throws IOException {
if(_summaryPropMaps == null) {
_summaryPropMaps = getPropertiesForDbObject(OBJECT_NAME_SUMMARY_PROPS);
@@ -1411,6 +1489,7 @@ public class DatabaseImpl implements Database
return _summaryPropMaps.getDefault();
}
+ @Override
public PropertyMap getUserDefinedProperties() throws IOException {
if(_userDefPropMaps == null) {
_userDefPropMaps = getPropertiesForDbObject(OBJECT_NAME_USERDEF_PROPS);
@@ -1488,6 +1567,7 @@ public class DatabaseImpl implements Database
return getPropsHandler().read(propsBytes, objectId, rowId, owner);
}
+ @Override
public String getDatabasePassword() throws IOException
{
ByteBuffer buffer = takeSharedBuffer();
@@ -1754,6 +1834,7 @@ public class DatabaseImpl implements Database
return CursorImpl.createCursor(table);
}
+ @Override
public void flush() throws IOException {
if(_linkedDbs != null) {
for(Database linkedDb : _linkedDbs.values()) {
@@ -1763,6 +1844,7 @@ public class DatabaseImpl implements Database
_pageChannel.flush();
}
+ @Override
public void close() throws IOException {
if(_linkedDbs != null) {
for(Database linkedDb : _linkedDbs.values()) {
@@ -1968,16 +2050,8 @@ public class DatabaseImpl implements Database
*/
public static Table.ColumnOrder getDefaultColumnOrder()
{
- String coProp = System.getProperty(COLUMN_ORDER_PROPERTY);
- if(coProp != null) {
- coProp = coProp.trim();
- if(coProp.length() > 0) {
- return Table.ColumnOrder.valueOf(coProp);
- }
- }
-
- // use default order
- return DEFAULT_COLUMN_ORDER;
+ return getEnumSystemProperty(Table.ColumnOrder.class, COLUMN_ORDER_PROPERTY,
+ DEFAULT_COLUMN_ORDER);
}
/**
@@ -2026,6 +2100,17 @@ public class DatabaseImpl implements Database
}
/**
+ * Returns the default DateTimeType. This defaults to
+ * {@link DateTimeType#DATE}, but can be overridden using the system
+ * property {@value com.healthmarketscience.jackcess.Database#DATE_TIME_TYPE_PROPERTY}.
+ * @usage _advanced_method_
+ */
+ public static DateTimeType getDefaultDateTimeType() {
+ return getEnumSystemProperty(DateTimeType.class, DATE_TIME_TYPE_PROPERTY,
+ DateTimeType.DATE);
+ }
+
+ /**
* Copies the given db InputStream to the given channel using the most
* efficient means possible.
*/
@@ -2106,11 +2191,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) {
@@ -2121,6 +2206,19 @@ public class DatabaseImpl implements Database
return msg + " (Db=" + dbName + ")";
}
+ private static <E extends Enum<E>> E getEnumSystemProperty(
+ Class<E> enumClass, String propName, E defaultValue)
+ {
+ String prop = System.getProperty(propName);
+ if(prop != null) {
+ prop = prop.trim().toUpperCase();
+ if(!prop.isEmpty()) {
+ return Enum.valueOf(enumClass, prop);
+ }
+ }
+ return defaultValue;
+ }
+
/**
* Utility class for storing table page number and actual name.
*/
@@ -2136,26 +2234,32 @@ public class DatabaseImpl implements Database
flags = newFlags;
}
+ @Override
public String getName() {
return tableName;
}
+ @Override
public boolean isLinked() {
return false;
}
+ @Override
public boolean isSystem() {
return isSystemObject(flags);
}
+ @Override
public String getLinkedTableName() {
return null;
}
+ @Override
public String getLinkedDbName() {
return null;
}
+ @Override
public Table open(Database db) throws IOException {
return ((DatabaseImpl)db).getTable(this, true);
}
@@ -2219,14 +2323,17 @@ public class DatabaseImpl implements Database
_tableNameIter = tableNames.iterator();
}
+ @Override
public boolean hasNext() {
return _tableNameIter.hasNext();
}
+ @Override
public void remove() {
throw new UnsupportedOperationException();
}
+ @Override
public Table next() {
if(!hasNext()) {
throw new NoSuchElementException();
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DateTimeContext.java b/src/main/java/com/healthmarketscience/jackcess/impl/DateTimeContext.java
new file mode 100644
index 0000000..8045755
--- /dev/null
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/DateTimeContext.java
@@ -0,0 +1,34 @@
+/*
+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 DateTimeContext
+{
+ public ZoneId getZoneId();
+
+ public TimeZone getTimeZone();
+
+ public ColumnImpl.DateTimeFactory getDateTimeFactory();
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DefaultCodecProvider.java b/src/main/java/com/healthmarketscience/jackcess/impl/DefaultCodecProvider.java
index 811f5b4..11854e7 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/DefaultCodecProvider.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/DefaultCodecProvider.java
@@ -48,6 +48,7 @@ public class DefaultCodecProvider implements CodecProvider
* This implementation returns DUMMY_HANDLER for databases with no encoding
* and UNSUPPORTED_HANDLER for databases with any encoding.
*/
+ @Override
public CodecHandler createHandler(PageChannel channel, Charset charset)
throws IOException
{
@@ -80,14 +81,17 @@ public class DefaultCodecProvider implements CodecProvider
*/
public static class DummyHandler implements CodecHandler
{
+ @Override
public boolean canEncodePartialPage() {
return true;
}
+ @Override
public boolean canDecodeInline() {
return true;
}
+ @Override
public void decodePage(ByteBuffer inPage, ByteBuffer outPage,
int pageNumber)
throws IOException
@@ -95,6 +99,7 @@ public class DefaultCodecProvider implements CodecProvider
// does nothing
}
+ @Override
public ByteBuffer encodePage(ByteBuffer page, int pageNumber,
int pageOffset)
throws IOException
@@ -111,14 +116,17 @@ public class DefaultCodecProvider implements CodecProvider
*/
public static class UnsupportedHandler implements CodecHandler
{
+ @Override
public boolean canEncodePartialPage() {
return true;
}
+ @Override
public boolean canDecodeInline() {
return true;
}
+ @Override
public void decodePage(ByteBuffer inPage, ByteBuffer outPage,
int pageNumber)
throws IOException
@@ -126,6 +134,7 @@ public class DefaultCodecProvider implements CodecProvider
throw new UnsupportedCodecException("Decoding not supported. Please choose a CodecProvider which supports reading the current database encoding.");
}
+ @Override
public ByteBuffer encodePage(ByteBuffer page, int pageNumber,
int pageOffset)
throws IOException
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java
index 21024e2..6c58182 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java
@@ -129,10 +129,12 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
return _indexEntryPattern;
}
+ @Override
public IndexImpl getIndex() {
return _index;
}
+ @Override
public Row findRowByEntry(Object... entryValues)
throws IOException
{
@@ -142,6 +144,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
return null;
}
+ @Override
public boolean findFirstRowByEntry(Object... entryValues)
throws IOException
{
@@ -163,6 +166,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
}
}
+ @Override
public void findClosestRowByEntry(Object... entryValues)
throws IOException
{
@@ -184,12 +188,14 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
}
}
+ @Override
public boolean currentRowMatchesEntry(Object... entryValues)
throws IOException
{
return currentRowMatchesEntryImpl(toRowValues(entryValues), _columnMatcher);
}
+ @Override
public EntryIterableBuilder newEntryIterable(Object... entryValues) {
return new EntryIterableBuilder(this, entryValues);
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java
index e2b2dd5..ca91fef 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,9 +104,10 @@ public class IndexData {
than valid RowIds */
ALWAYS_LAST;
}
-
+
public static final Comparator<byte[]> BYTE_CODE_COMPARATOR =
new Comparator<byte[]>() {
+ @Override
public int compare(byte[] left, byte[] right) {
if(left == right) {
return 0;
@@ -131,8 +132,8 @@ public class IndexData {
((left.length > right.length) ? 1 : 0));
}
};
-
-
+
+
/** name, generated on demand */
private String _name;
/** owning table */
@@ -175,7 +176,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 +217,14 @@ public class IndexData {
} else {
_name = String.valueOf(_number);
}
- }
+ }
return _name;
}
public TableImpl getTable() {
return _table;
}
-
+
public JetFormat getFormat() {
return getTable().getFormat();
}
@@ -280,7 +281,7 @@ public class IndexData {
public int getIndexDataNumber() {
return _number;
}
-
+
public int getUniqueEntryCount() {
return _uniqueEntryCount;
}
@@ -315,7 +316,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 +346,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 +372,7 @@ public class IndexData {
public int getOwnedPageCount() {
return _ownedPages.getPageCount();
}
-
+
void addOwnedPage(int pageNumber) throws IOException {
_ownedPages.addPageNumber(pageNumber);
}
@@ -379,7 +380,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 +408,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 +430,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 +471,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 +526,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 +560,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 +569,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 +578,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 +592,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 +604,7 @@ public class IndexData {
{
return prepareAddRow(row, rowId, new AddRowPendingChange(nextChange));
}
-
+
private PendingChange prepareAddRow(Object[] row, RowIdImpl rowId,
AddRowPendingChange change)
throws IOException
@@ -619,7 +620,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 +638,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 +692,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 +700,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 +716,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 +730,7 @@ public class IndexData {
{
deleteRowImpl(row, rowId);
}
-
+
private Entry deleteRowImpl(Object[] row, RowIdImpl rowId)
throws IOException
{
@@ -738,7 +739,7 @@ public class IndexData {
// nothing to do
return null;
}
-
+
// make sure we've parsed the entries
initialize();
@@ -748,7 +749,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 +774,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 +811,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 +828,7 @@ public class IndexData {
change = change.getNext();
}
}
-
+
/**
* Gets a new cursor for this index.
* <p>
@@ -838,13 +839,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 +1009,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 +1058,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 +1089,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 +1117,7 @@ public class IndexData {
sb.append("pageCache", _pageCache);
return sb.toString();
}
-
+
/**
* Write the given index page out to a buffer
*/
@@ -1126,7 +1127,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 +1171,7 @@ public class IndexData {
// first entry includes the prefix
buffer.put(entryPrefix);
-
+
for(Entry entry : dataPage.getEntries()) {
entry.write(buffer, entryPrefix);
}
@@ -1232,7 +1233,7 @@ public class IndexData {
"Unexpected order in index entries, " +
prevEntry + " >= " + entry));
}
-
+
entries.add(entry);
if((entries.size() == 1) && (entryPrefixLength > 0)) {
@@ -1251,7 +1252,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 +1266,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 +1292,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 +1310,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 +1320,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 +1331,7 @@ public class IndexData {
++nullCount;
}
}
-
+
return nullCount;
}
@@ -1342,12 +1343,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 +1374,9 @@ public class IndexData {
col.writeValue(value, _entryBuffer);
}
-
+
return _entryBuffer.toByteArray();
- }
+ }
/**
* Finds the data page for the given entry.
@@ -1385,7 +1386,7 @@ public class IndexData {
{
return _pageCache.findCacheDataPage(entry);
}
-
+
/**
* Gets the data page for the pageNumber.
*/
@@ -1394,7 +1395,7 @@ public class IndexData {
{
return _pageCache.getCacheDataPage(pageNumber);
}
-
+
/**
* Flips the first bit in the byte at the given index.
*/
@@ -1418,7 +1419,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 +1431,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 +1453,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 +1550,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 +1599,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.
@@ -1615,6 +1616,7 @@ public class IndexData {
_flags = flags;
}
+ @Override
public ColumnImpl getColumn() {
return _column;
}
@@ -1623,14 +1625,17 @@ public class IndexData {
return _flags;
}
+ @Override
public boolean isAscending() {
return((getFlags() & ASCENDING_COLUMN_FLAG) != 0);
}
-
+
+ @Override
public int getColumnIndex() {
return getColumn().getColumnIndex();
}
-
+
+ @Override
public String getName() {
return getColumn().getName();
}
@@ -1638,7 +1643,7 @@ public class IndexData {
protected boolean isNullValue(Object value) {
return (value == null);
}
-
+
protected final void writeValue(Object value, ByteStream bout)
throws IOException
{
@@ -1647,7 +1652,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 +1660,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 +1681,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 +1712,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 +1728,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 +1760,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 +1778,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 +1787,9 @@ public class IndexData {
handleNegationAndOrder(isNegative, valueBytes);
bout.write(valueBytes);
- }
+ }
}
-
+
/**
* ColumnDescriptor for new-style fixed point based columns.
*/
@@ -1796,7 +1801,7 @@ public class IndexData {
{
super(column, flags);
}
-
+
@Override
protected void handleNegationAndOrder(boolean isNegative,
byte[] valueBytes)
@@ -1809,9 +1814,9 @@ public class IndexData {
if(isNegative == isAscending()) {
flipBytes(valueBytes);
}
- }
+ }
}
-
+
/**
* ColumnDescriptor for byte based columns.
*/
@@ -1822,24 +1827,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 +1861,7 @@ public class IndexData {
// null values are handled as booleans
return false;
}
-
+
@Override
protected void writeNonNullValue(Object value, ByteStream bout)
throws IOException
@@ -1867,11 +1872,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 +1884,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 +1904,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 +1924,7 @@ public class IndexData {
{
super(column, flags);
}
-
+
@Override
protected void writeNonNullValue(Object value, ByteStream bout)
throws IOException
@@ -1929,7 +1934,7 @@ public class IndexData {
bout);
}
}
-
+
/**
* ColumnDescriptor for BINARY columns.
@@ -1950,8 +1955,8 @@ public class IndexData {
ColumnImpl.toByteArray(value), isAscending(), bout);
}
}
-
-
+
+
/**
* ColumnDescriptor for columns which we cannot currently write.
*/
@@ -1971,7 +1976,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 +1988,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 +2000,7 @@ public class IndexData {
_entryBytes = entryBytes;
_type = type;
}
-
+
/**
* Create a new entry
* @param entryBytes encoded bytes for this index entry
@@ -2014,7 +2019,7 @@ public class IndexData {
{
this(buffer, entryLen, 0);
}
-
+
/**
* Read an existing entry in from a buffer
*/
@@ -2031,11 +2036,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 +2052,11 @@ public class IndexData {
public Integer getSubPageNumber() {
throw new UnsupportedOperationException();
}
-
+
public boolean isLeafEntry() {
return true;
}
-
+
public boolean isValid() {
return(_entryBytes != null);
}
@@ -2059,7 +2064,7 @@ public class IndexData {
protected final byte[] getEntryBytes() {
return _entryBytes;
}
-
+
/**
* Size of this entry in the db.
*/
@@ -2067,7 +2072,7 @@ public class IndexData {
// need 4 trailing bytes for the rowId
return _entryBytes.length + 4;
}
-
+
/**
* Write this entry into a buffer
*/
@@ -2076,15 +2081,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 +2098,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 +2118,7 @@ public class IndexData {
}
return sb;
}
-
+
@Override
public String toString() {
return entryBytesToStringBuilder(
@@ -2141,7 +2146,8 @@ public class IndexData {
public boolean equalsEntryBytes(Entry o) {
return(BYTE_CODE_COMPARATOR.compare(_entryBytes, o._entryBytes) == 0);
}
-
+
+ @Override
public int compareTo(Entry other) {
if (this == other) {
return 0;
@@ -2165,7 +2171,7 @@ public class IndexData {
return typeCmp;
}
}
-
+
// at this point we let the RowId decide the final result
return _rowId.compareTo(other.getRowId());
}
@@ -2177,7 +2183,7 @@ public class IndexData {
protected Entry asNodeEntry(Integer subPageNumber) {
return new NodeEntry(_entryBytes, _rowId, _type, subPageNumber);
}
-
+
}
/**
@@ -2200,7 +2206,7 @@ public class IndexData {
super(entryBytes, rowId, type);
_subPageNumber = subPageNumber;
}
-
+
/**
* Read an existing node entry in from a buffer
*/
@@ -2222,19 +2228,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 +2256,7 @@ public class IndexData {
.append("rowId", getRowId())
.append("subPage", _subPageNumber))
.toString();
- }
+ }
}
/**
@@ -2298,14 +2304,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 +2326,7 @@ public class IndexData {
public boolean isUpToDate() {
return(getIndexModCount() == _lastModCount);
}
-
+
public void reset() {
beforeFirst();
}
@@ -2341,26 +2347,26 @@ public class IndexData {
/**
* Repositions the cursor so that the next row will be the first entry
- * >= the given row.
+ * &gt;= 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 &lt;= 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 +2392,7 @@ public class IndexData {
{
restorePosition(curEntry, _curPos.getEntry());
}
-
+
/**
* Restores a current and previous position for the cursor.
*/
@@ -2406,7 +2412,7 @@ public class IndexData {
checkForModification();
}
}
-
+
/**
* Gets another entry in the given direction, returning the new entry.
*/
@@ -2462,7 +2468,7 @@ public class IndexData {
withErrorContext("Invalid entry given " + entry));
}
}
-
+
Position pos = findEntryPosition(entry);
if(pos.compareTo(_lastPos) >= 0) {
return _lastPos;
@@ -2471,7 +2477,7 @@ public class IndexData {
}
return pos;
}
-
+
/**
* Updates any the boundary info (_firstPos/_lastPos).
*/
@@ -2481,7 +2487,7 @@ public class IndexData {
_firstPos = findEntryPosition(_firstPos.getEntry());
_lastPos = findEntryPosition(_lastPos.getEntry());
}
-
+
@Override
public String toString() {
return CustomToStringStyle.valueBuilder(this)
@@ -2489,7 +2495,7 @@ public class IndexData {
.append("prevPosition", _prevPos)
.toString();
}
-
+
/**
* Handles moving the cursor in a given direction. Separates cursor
* logic from value storage.
@@ -2500,7 +2506,7 @@ public class IndexData {
public abstract Position getBeginningPosition();
public abstract Position getEndPosition();
}
-
+
/**
* Handles moving the cursor forward.
*/
@@ -2524,7 +2530,7 @@ public class IndexData {
return _lastPos;
}
}
-
+
/**
* Handles moving the cursor backward.
*/
@@ -2569,7 +2575,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 +2587,7 @@ public class IndexData {
public DataPage getDataPage() {
return _dataPage;
}
-
+
public int getIndex() {
return _idx;
}
@@ -2598,7 +2604,7 @@ public class IndexData {
// non-between case
return(_idx - 1);
}
-
+
public Entry getEntry() {
return _entry;
}
@@ -2610,7 +2616,8 @@ public class IndexData {
public boolean equalsEntry(Entry entry) {
return _entry.equals(entry);
}
-
+
+ @Override
public int compareTo(Position other)
{
if(this == other) {
@@ -2627,16 +2634,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 +2668,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 +2678,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 +2695,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 +2724,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 +2747,7 @@ public class IndexData {
@Override
public int getPageNumber() { return 0; }
-
+
@Override
public boolean isLeaf() { return true; }
@Override
@@ -2760,7 +2767,7 @@ public class IndexData {
public int getChildTailPageNumber() { return 0; }
@Override
public void setChildTailPageNumber(int pageNumber) { }
-
+
@Override
public int getTotalEntrySize() { return 0; }
@Override
@@ -2772,7 +2779,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 +2807,7 @@ public class IndexData {
public PendingChange getNext() {
return _next;
}
-
+
/**
* Completes the pending change.
*/
@@ -2827,7 +2834,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 +2850,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..60d80d5 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;
@@ -112,6 +112,7 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
return _data;
}
+ @Override
public TableImpl getTable() {
return getIndexData().getTable();
}
@@ -140,6 +141,7 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
return getIndexData().getUniqueEntryCountOffset();
}
+ @Override
public String getName() {
return _name;
}
@@ -148,10 +150,12 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
_name = name;
}
+ @Override
public boolean isPrimaryKey() {
return _indexType == PRIMARY_KEY_INDEX_TYPE;
}
+ @Override
public boolean isForeignKey() {
return _indexType == FOREIGN_KEY_INDEX_TYPE;
}
@@ -160,6 +164,7 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
return _reference;
}
+ @Override
public IndexImpl getReferencedIndex() throws IOException {
if(_reference == null) {
@@ -204,26 +209,32 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
return refIndex;
}
+ @Override
public boolean shouldIgnoreNulls() {
return getIndexData().shouldIgnoreNulls();
}
+ @Override
public boolean isUnique() {
return getIndexData().isUnique();
}
+ @Override
public boolean isRequired() {
return getIndexData().isRequired();
}
+ @Override
public List<IndexData.ColumnDescriptor> getColumns() {
return getIndexData().getColumns();
}
+ @Override
public int getColumnCount() {
return getIndexData().getColumnCount();
}
+ @Override
public CursorBuilder newCursor() {
return getTable().newCursor().setIndex(this);
}
@@ -367,6 +378,7 @@ public class IndexImpl implements Index, Comparable<IndexImpl>
return sb.toString();
}
+ @Override
public int compareTo(IndexImpl other) {
if (_indexNumber > other.getIndexNumber()) {
return 1;
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/InternalColumnValidator.java b/src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java
index 3d4dab9..1755949 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java
@@ -53,6 +53,7 @@ abstract class InternalColumnValidator implements ColumnValidator
intValidator._delegate = extValidator;
}
+ @Override
public final Object validate(Column col, Object val) throws IOException {
val = _delegate.validate(col, val);
return internalValidate(col, val);
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java b/src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java
index d8a2336..0d61923 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.
@@ -471,10 +471,12 @@ public class OleUtil
_bytes = bytes;
}
+ @Override
public void writeTo(OutputStream out) throws IOException {
out.write(_bytes);
}
+ @Override
public Content getContent() throws IOException {
if(_content == null) {
_content = parseContent(this);
@@ -482,16 +484,19 @@ public class OleUtil
return _content;
}
+ @Override
public InputStream getBinaryStream() throws SQLException {
return new ByteArrayInputStream(_bytes);
}
+ @Override
public InputStream getBinaryStream(long pos, long len)
throws SQLException
{
return new ByteArrayInputStream(_bytes, fromJdbcOffset(pos), (int)len);
}
+ @Override
public long length() throws SQLException {
return _bytes.length;
}
@@ -503,41 +508,50 @@ public class OleUtil
return _bytes;
}
+ @Override
public byte[] getBytes(long pos, int len) throws SQLException {
return ByteUtil.copyOf(_bytes, fromJdbcOffset(pos), len);
}
+ @Override
public long position(byte[] pattern, long start) throws SQLException {
int pos = ByteUtil.findRange(PageChannel.wrap(_bytes),
fromJdbcOffset(start), pattern);
return((pos >= 0) ? toJdbcOffset(pos) : pos);
}
+ @Override
public long position(Blob pattern, long start) throws SQLException {
return position(pattern.getBytes(1L, (int)pattern.length()), start);
}
+ @Override
public OutputStream setBinaryStream(long position) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
+ @Override
public void truncate(long len) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
+ @Override
public int setBytes(long pos, byte[] bytes) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
+ @Override
public int setBytes(long pos, byte[] bytes, int offset, int lesn)
throws SQLException {
throw new SQLFeatureNotSupportedException();
}
+ @Override
public void free() {
close();
}
+ @Override
public void close() {
_bytes = null;
ByteUtil.closeQuietly(_content);
@@ -573,6 +587,7 @@ public class OleUtil
_blob = blob;
}
+ @Override
public OleBlobImpl getBlob() {
return _blob;
}
@@ -581,6 +596,7 @@ public class OleUtil
return getBlob().getBytes();
}
+ @Override
public void close() {
// base does nothing
}
@@ -604,14 +620,17 @@ public class OleUtil
_length = length;
}
+ @Override
public long length() {
return _length;
}
+ @Override
public InputStream getStream() throws IOException {
return new ByteArrayInputStream(getBytes(), _position, _length);
}
+ @Override
public void writeTo(OutputStream out) throws IOException {
out.write(getBytes(), _position, _length);
}
@@ -644,14 +663,17 @@ public class OleUtil
_typeName = typeName;
}
+ @Override
public String getPrettyName() {
return _prettyName;
}
+ @Override
public String getClassName() {
return _className;
}
+ @Override
public String getTypeName() {
return _typeName;
}
@@ -685,22 +707,27 @@ public class OleUtil
_filePath = filePath;
}
+ @Override
public ContentType getType() {
return ContentType.LINK;
}
+ @Override
public String getFileName() {
return _fileName;
}
+ @Override
public String getLinkPath() {
return _linkPath;
}
+ @Override
public String getFilePath() {
return _filePath;
}
+ @Override
public InputStream getLinkStream() throws IOException {
return new FileInputStream(getLinkPath());
}
@@ -735,18 +762,22 @@ public class OleUtil
_localFilePath = localFilePath;
}
+ @Override
public ContentType getType() {
return ContentType.SIMPLE_PACKAGE;
}
+ @Override
public String getFileName() {
return _fileName;
}
+ @Override
public String getFilePath() {
return _filePath;
}
+ @Override
public String getLocalFilePath() {
return _localFilePath;
}
@@ -772,6 +803,7 @@ public class OleUtil
super(blob, prettyName, className, typeName, position, length);
}
+ @Override
public ContentType getType() {
return ContentType.OTHER;
}
@@ -789,6 +821,7 @@ public class OleUtil
super(blob);
}
+ @Override
public ContentType getType() {
return ContentType.UNKNOWN;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java b/src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java
index 19d15fd..41c164c 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java
@@ -389,10 +389,12 @@ public class PageChannel implements Channel, Flushable {
return ByteBuffer.allocate(size).order(order);
}
+ @Override
public void flush() throws IOException {
_channel.force(true);
}
+ @Override
public void close() throws IOException {
flush();
if(_closeChannel) {
@@ -400,6 +402,7 @@ public class PageChannel implements Channel, Flushable {
}
}
+ @Override
public boolean isOpen() {
return _channel.isOpen();
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java
index be3a249..a092e82 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;
@@ -73,6 +74,7 @@ public class PropertyMapImpl implements PropertyMap
_owner = owner;
}
+ @Override
public String getName() {
return _mapName;
}
@@ -85,22 +87,27 @@ public class PropertyMapImpl implements PropertyMap
return _owner;
}
+ @Override
public int getSize() {
return _props.size();
}
+ @Override
public boolean isEmpty() {
return _props.isEmpty();
}
+ @Override
public Property get(String name) {
return _props.get(DatabaseImpl.toLookupName(name));
}
+ @Override
public Object getValue(String name) {
return getValue(name, null);
}
+ @Override
public Object getValue(String name, Object defaultValue) {
Property prop = get(name);
Object value = defaultValue;
@@ -110,14 +117,17 @@ public class PropertyMapImpl implements PropertyMap
return value;
}
+ @Override
public PropertyImpl put(String name, Object value) {
return put(name, null, value, false);
}
+ @Override
public PropertyImpl put(String name, DataType type, Object value) {
return put(name, type, value, false);
}
+ @Override
public void putAll(Iterable<? extends Property> props) {
if(props == null) {
return;
@@ -135,6 +145,7 @@ public class PropertyMapImpl implements PropertyMap
/**
* Puts a property into this map with the given information.
*/
+ @Override
public PropertyImpl put(String name, DataType type, Object value,
boolean isDdl) {
PropertyImpl prop = (PropertyImpl)createProperty(name, type, value, isDdl);
@@ -142,14 +153,17 @@ public class PropertyMapImpl implements PropertyMap
return prop;
}
+ @Override
public PropertyImpl remove(String name) {
return (PropertyImpl)_props.remove(DatabaseImpl.toLookupName(name));
}
+ @Override
public Iterator<Property> iterator() {
return _props.values().iterator();
}
+ @Override
public void save() throws IOException {
getOwner().save();
}
@@ -208,7 +222,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;
@@ -242,22 +256,27 @@ public class PropertyMapImpl implements PropertyMap
_value = value;
}
+ @Override
public String getName() {
return _name;
}
+ @Override
public DataType getType() {
return _type;
}
+ @Override
public Object getValue() {
return _value;
}
+ @Override
public void setValue(Object newValue) {
_value = newValue;
}
+ @Override
public boolean isDdl() {
return _ddl;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java
index 61e1e07..e545574 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java
@@ -103,6 +103,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
return map;
}
+ @Override
public Iterator<PropertyMapImpl> iterator() {
return _maps.values().iterator();
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java
index 0cc2b90..a9342f8 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java
@@ -83,22 +83,27 @@ public class RelationshipImpl implements Relationship
_flags = flags;
}
+ @Override
public String getName() {
return _name;
}
+ @Override
public Table getFromTable() {
return _fromTable;
}
+ @Override
public List<Column> getFromColumns() {
return _fromColumns;
}
+ @Override
public Table getToTable() {
return _toTable;
}
+ @Override
public List<Column> getToColumns() {
return _toColumns;
}
@@ -107,34 +112,42 @@ public class RelationshipImpl implements Relationship
return _flags;
}
+ @Override
public boolean isOneToOne() {
return hasFlag(ONE_TO_ONE_FLAG);
}
+ @Override
public boolean hasReferentialIntegrity() {
return !hasFlag(NO_REFERENTIAL_INTEGRITY_FLAG);
}
+ @Override
public boolean cascadeUpdates() {
return hasFlag(CASCADE_UPDATES_FLAG);
}
+ @Override
public boolean cascadeDeletes() {
return hasFlag(CASCADE_DELETES_FLAG);
}
+ @Override
public boolean cascadeNullOnDelete() {
return hasFlag(CASCADE_NULL_FLAG);
}
+ @Override
public boolean isLeftOuterJoin() {
return hasFlag(LEFT_OUTER_JOIN_FLAG);
}
+ @Override
public boolean isRightOuterJoin() {
return hasFlag(RIGHT_OUTER_JOIN_FLAG);
}
+ @Override
public JoinType getJoinType() {
if(isLeftOuterJoin()) {
return JoinType.LEFT_OUTER;
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/RowIdImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/RowIdImpl.java
index 7bfab4a..097823a 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;
/**
@@ -98,6 +98,7 @@ public class RowIdImpl implements RowId, Serializable
return _type;
}
+ @Override
public int compareTo(RowId other) {
return compareTo((RowIdImpl)other);
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/RowImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/RowImpl.java
index ee088f4..e89b070 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-&gt;value pairs.
+ * <p>
* 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;
@@ -54,54 +55,73 @@ public class RowImpl extends LinkedHashMap<String,Object> implements Row
_id = (RowIdImpl)row.getId();
}
+ @Override
public RowIdImpl getId() {
return _id;
}
+ @Override
public String getString(String name) {
return (String)get(name);
}
+ @Override
public Boolean getBoolean(String name) {
return (Boolean)get(name);
}
+ @Override
public Byte getByte(String name) {
return (Byte)get(name);
}
+ @Override
public Short getShort(String name) {
return (Short)get(name);
}
+ @Override
public Integer getInt(String name) {
return (Integer)get(name);
}
+ @Override
public BigDecimal getBigDecimal(String name) {
return (BigDecimal)get(name);
}
+ @Override
public Float getFloat(String name) {
return (Float)get(name);
}
+ @Override
public Double getDouble(String name) {
return (Double)get(name);
}
+ @Override
+ @SuppressWarnings("deprecation")
public Date getDate(String name) {
return (Date)get(name);
}
+ @Override
+ public LocalDateTime getLocalDateTime(String name) {
+ return (LocalDateTime)get(name);
+ }
+
+ @Override
public byte[] getBytes(String name) {
return (byte[])get(name);
}
+ @Override
public ComplexValueForeignKey getForeignKey(String name) {
return (ComplexValueForeignKey)get(name);
}
+ @Override
public OleBlob getBlob(String name) throws IOException {
byte[] bytes = getBytes(name);
return ((bytes != null) ? OleBlob.Builder.fromInternalData(bytes) : null);
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java
index 15a0c8c..eafa376 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;
@@ -97,6 +97,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
the variable length offset table */
private static final Comparator<ColumnImpl> VAR_LEN_COLUMN_COMPARATOR =
new Comparator<ColumnImpl>() {
+ @Override
public int compare(ColumnImpl c1, ColumnImpl c2) {
return ((c1.getVarLenTableIndex() < c2.getVarLenTableIndex()) ? -1 :
((c1.getVarLenTableIndex() > c2.getVarLenTableIndex()) ? 1 :
@@ -107,6 +108,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
/** comparator which sorts columns based on their display index */
private static final Comparator<ColumnImpl> DISPLAY_ORDER_COMPARATOR =
new Comparator<ColumnImpl>() {
+ @Override
public int compare(ColumnImpl c1, ColumnImpl c2) {
return ((c1.getDisplayIndex() < c2.getDisplayIndex()) ? -1 :
((c1.getDisplayIndex() > c2.getDisplayIndex()) ? 1 :
@@ -320,14 +322,17 @@ public class TableImpl implements Table, PropertyMaps.Owner
}
}
+ @Override
public String getName() {
return _name;
}
+ @Override
public boolean isHidden() {
return((_flags & DatabaseImpl.HIDDEN_OBJECT_FLAG) != 0);
}
+ @Override
public boolean isSystem() {
return(_tableType != TYPE_USER);
}
@@ -339,10 +344,12 @@ public class TableImpl implements Table, PropertyMaps.Owner
return _maxColumnCount;
}
+ @Override
public int getColumnCount() {
return _columns.size();
}
+ @Override
public DatabaseImpl getDatabase() {
return _database;
}
@@ -361,11 +368,13 @@ public class TableImpl implements Table, PropertyMaps.Owner
return getDatabase().getPageChannel();
}
+ @Override
public ErrorHandler getErrorHandler() {
return((_tableErrorHandler != null) ? _tableErrorHandler :
getDatabase().getErrorHandler());
}
+ @Override
public void setErrorHandler(ErrorHandler newErrorHandler) {
_tableErrorHandler = newErrorHandler;
}
@@ -374,11 +383,13 @@ public class TableImpl implements Table, PropertyMaps.Owner
return _tableDefPageNumber;
}
+ @Override
public boolean isAllowAutoNumberInsert() {
return ((_allowAutoNumInsert != null) ? (boolean)_allowAutoNumInsert :
getDatabase().isAllowAutoNumberInsert());
}
+ @Override
public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
_allowAutoNumInsert = allowAutoNumInsert;
}
@@ -432,10 +443,12 @@ public class TableImpl implements Table, PropertyMaps.Owner
return _longValueBufferH;
}
+ @Override
public List<ColumnImpl> getColumns() {
return Collections.unmodifiableList(_columns);
}
+ @Override
public ColumnImpl getColumn(String name) {
for(ColumnImpl column : _columns) {
if(column.getName().equalsIgnoreCase(name)) {
@@ -455,6 +468,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
return false;
}
+ @Override
public PropertyMap getProperties() throws IOException {
if(_props == null) {
_props = getPropertyMaps().getDefault();
@@ -474,6 +488,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
return _propertyMaps;
}
+ @Override
public void propertiesUpdated() throws IOException {
// propagate update to columns
for(ColumnImpl col : _columns) {
@@ -487,10 +502,12 @@ public class TableImpl implements Table, PropertyMaps.Owner
_calcColEval.reSort();
}
+ @Override
public List<IndexImpl> getIndexes() {
return Collections.unmodifiableList(_indexes);
}
+ @Override
public IndexImpl getIndex(String name) {
for(IndexImpl index : _indexes) {
if(index.getName().equalsIgnoreCase(name)) {
@@ -501,6 +518,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
"Index with name " + name + " does not exist on this table"));
}
+ @Override
public IndexImpl getPrimaryKeyIndex() {
for(IndexImpl index : _indexes) {
if(index.isPrimaryKey()) {
@@ -511,6 +529,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
"No primary key index found"));
}
+ @Override
public IndexImpl getForeignKeyIndex(Table otherTable) {
for(IndexImpl index : _indexes) {
if(index.isForeignKey() && (index.getReference() != null) &&
@@ -592,6 +611,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
return _autoNumColumns;
}
+ @Override
public CursorImpl getDefaultCursor() {
if(_defaultCursor == null) {
_defaultCursor = CursorImpl.createCursor(this);
@@ -599,14 +619,17 @@ public class TableImpl implements Table, PropertyMaps.Owner
return _defaultCursor;
}
+ @Override
public CursorBuilder newCursor() {
return new CursorBuilder(this);
}
+ @Override
public void reset() {
getDefaultCursor().reset();
}
+ @Override
public Row deleteRow(Row row) throws IOException {
deleteRow(row.getId());
return row;
@@ -690,6 +713,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
}
}
+ @Override
public Row getNextRow() throws IOException {
return getDefaultCursor().getNextRow();
}
@@ -1034,6 +1058,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
}
}
+ @Override
public Iterator<Row> iterator() {
return getDefaultCursor().iterator();
}
@@ -2081,12 +2106,13 @@ public class TableImpl implements Table, PropertyMaps.Owner
return ByteUtil.getUnsignedVarInt(buffer, getFormat().SIZE_NAME_LENGTH);
}
+ @Override
public Object[] asRow(Map<String,?> rowMap) {
return asRow(rowMap, null, false);
}
/**
- * Converts a map of columnName -> columnValue to an array of row values
+ * Converts a map of columnName -&gt; 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
@@ -2096,6 +2122,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
return asRow(rowMap, null, true);
}
+ @Override
public Object[] asUpdateRow(Map<String,?> rowMap) {
return asRow(rowMap, Column.KEEP_VALUE, false);
}
@@ -2110,7 +2137,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 -&gt; columnValue to an array of row values.
*/
private Object[] asRow(Map<String,?> rowMap, Object defaultValue,
boolean returnRowId)
@@ -2137,10 +2164,12 @@ public class TableImpl implements Table, PropertyMaps.Owner
return row;
}
+ @Override
public Object[] addRow(Object... row) throws IOException {
return addRows(Collections.singletonList(row), false).get(0);
}
+ @Override
public <M extends Map<String,Object>> M addRowFromMap(M row)
throws IOException
{
@@ -2152,12 +2181,14 @@ public class TableImpl implements Table, PropertyMaps.Owner
return row;
}
+ @Override
public List<? extends Object[]> addRows(List<? extends Object[]> rows)
throws IOException
{
return addRows(rows, true);
}
+ @Override
public <M extends Map<String,Object>> List<M> addRowsFromMaps(List<M> rows)
throws IOException
{
@@ -2387,6 +2418,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
return false;
}
+ @Override
public Row updateRow(Row row) throws IOException {
return updateRowFromMap(
getDefaultCursor().getRowState(), (RowIdImpl)row.getId(), row);
@@ -2998,6 +3030,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
}
}
+ @Override
public int getRowCount() {
return _rowCount;
}
@@ -3360,6 +3393,7 @@ public class TableImpl implements Table, PropertyMaps.Owner
_lastModCount = TableImpl.this._modCount;
}
+ @Override
public TableImpl getTable() {
return TableImpl.this;
}
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..3aff524 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;
@@ -264,6 +266,7 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment>
_encodedData = encodedData;
}
+ @Override
public byte[] getFileData() throws IOException {
if((_data == null) && (_encodedData != null)) {
_data = decodeData();
@@ -271,11 +274,13 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment>
return _data;
}
+ @Override
public void setFileData(byte[] data) {
_data = data;
_encodedData = null;
}
+ @Override
public byte[] getEncodedFileData() throws IOException {
if((_encodedData == null) && (_data != null)) {
_encodedData = encodeData();
@@ -283,55 +288,83 @@ public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment>
return _encodedData;
}
+ @Override
public void setEncodedFileData(byte[] data) {
_encodedData = data;
_data = null;
}
+ @Override
public String getFileName() {
return _name;
}
+ @Override
public void setFileName(String fileName) {
_name = fileName;
}
+ @Override
public String getFileUrl() {
return _url;
}
+ @Override
public void setFileUrl(String fileUrl) {
_url = fileUrl;
}
+ @Override
public String getFileType() {
return _type;
}
+ @Override
public void setFileType(String fileType) {
_type = fileType;
}
+ @Override
public Date getFileTimeStamp() {
- return _timeStamp;
+ return (Date)_timeStamp;
}
+ @Override
public void setFileTimeStamp(Date fileTimeStamp) {
_timeStamp = fileTimeStamp;
}
+ @Override
+ public LocalDateTime getFileLocalTimeStamp() {
+ return (LocalDateTime)_timeStamp;
+ }
+
+ @Override
+ public void setFileLocalTimeStamp(LocalDateTime fileTimeStamp) {
+ _timeStamp = fileTimeStamp;
+ }
+
+ @Override
+ public Object getFileTimeStampObject() {
+ return _timeStamp;
+ }
+
+ @Override
public Integer getFileFlags() {
return _flags;
}
+ @Override
public void setFileFlags(Integer fileFlags) {
_flags = fileFlags;
}
+ @Override
public void update() throws IOException {
getComplexValueForeignKey().updateAttachment(this);
}
+ @Override
public void delete() throws IOException {
getComplexValueForeignKey().deleteAttachment(this);
}
@@ -348,7 +381,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/ComplexColumnInfoImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexColumnInfoImpl.java
index 566af72..31c0e04 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexColumnInfoImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexColumnInfoImpl.java
@@ -122,12 +122,14 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
return _typeCols;
}
+ @Override
public int countValues(int complexValueFk) throws IOException {
return getRawValues(complexValueFk,
Collections.singleton(_complexValFkCol.getName()))
.size();
}
+ @Override
public List<Row> getRawValues(int complexValueFk)
throws IOException
{
@@ -148,6 +150,7 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
.setColumnNames(columnNames).iterator();
}
+ @Override
public List<Row> getRawValues(int complexValueFk,
Collection<String> columnNames)
throws IOException
@@ -166,6 +169,7 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
return values;
}
+ @Override
public List<V> getValues(ComplexValueForeignKey complexValueFk)
throws IOException
{
@@ -189,6 +193,7 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
return values;
}
+ @Override
public ComplexValue.Id addRawValue(Map<String,?> rawValue)
throws IOException
{
@@ -197,6 +202,7 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
return getValueId(row);
}
+ @Override
public ComplexValue.Id addValue(V value) throws IOException {
Object[] row = asRow(newRowArray(), value);
_flatTable.addRow(row);
@@ -205,43 +211,51 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
return id;
}
+ @Override
public void addValues(Collection<? extends V> values) throws IOException {
for(V value : values) {
addValue(value);
}
}
+ @Override
public ComplexValue.Id updateRawValue(Row rawValue) throws IOException {
_flatTable.updateRow(rawValue);
return getValueId(rawValue);
}
+ @Override
public ComplexValue.Id updateValue(V value) throws IOException {
ComplexValue.Id id = value.getId();
updateRow(id, asRow(newRowArray(), value));
return id;
}
+ @Override
public void updateValues(Collection<? extends V> values) throws IOException {
for(V value : values) {
updateValue(value);
}
}
+ @Override
public void deleteRawValue(Row rawValue) throws IOException {
deleteRow(rawValue.getId());
}
+ @Override
public void deleteValue(V value) throws IOException {
deleteRow(value.getId().getRowId());
}
+ @Override
public void deleteValues(Collection<? extends V> values) throws IOException {
for(V value : values) {
deleteValue(value);
}
}
+ @Override
public void deleteAllValues(int complexValueFk) throws IOException {
Iterator<Row> entryIter =
getComplexValFkIter(complexValueFk, Collections.<String>emptySet());
@@ -255,6 +269,7 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
}
}
+ @Override
public void deleteAllValues(ComplexValueForeignKey complexValueFk)
throws IOException
{
@@ -322,6 +337,7 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
}
}
+ @Override
public abstract ComplexDataType getType();
protected abstract V toValue(
@@ -339,10 +355,12 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
_complexValueFk = complexValueFk;
}
+ @Override
public Id getId() {
return _id;
}
+ @Override
public void setId(Id id) {
if(_id == id) {
// harmless, ignore
@@ -354,10 +372,12 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
_id = id;
}
+ @Override
public ComplexValueForeignKey getComplexValueForeignKey() {
return _complexValueFk;
}
+ @Override
public void setComplexValueForeignKey(ComplexValueForeignKey complexValueFk)
{
if(_complexValueFk == complexValueFk) {
@@ -370,6 +390,7 @@ public abstract class ComplexColumnInfoImpl<V extends ComplexValue>
_complexValueFk = complexValueFk;
}
+ @Override
public Column getColumn() {
return _complexValueFk.getColumn();
}
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/MultiValueColumnInfoImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnInfoImpl.java
index 809cf88..65d77d8 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnInfoImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnInfoImpl.java
@@ -96,18 +96,22 @@ public class MultiValueColumnInfoImpl extends ComplexColumnInfoImpl<SingleValue>
_value = value;
}
+ @Override
public Object get() {
return _value;
}
+ @Override
public void set(Object value) {
_value = value;
}
+ @Override
public void update() throws IOException {
getComplexValueForeignKey().updateMultiValue(this);
}
+ @Override
public void delete() throws IOException {
getComplexValueForeignKey().deleteMultiValue(this);
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnPropertyMap.java b/src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnPropertyMap.java
index 7adc2e3..49aed52 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnPropertyMap.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnPropertyMap.java
@@ -49,18 +49,22 @@ public class MultiValueColumnPropertyMap implements PropertyMap
_complex = complex;
}
+ @Override
public String getName() {
return _primary.getName();
}
+ @Override
public int getSize() {
return _primary.getSize() + _complex.getSize();
}
+ @Override
public boolean isEmpty() {
return _primary.isEmpty() && _complex.isEmpty();
}
+ @Override
public Property get(String name) {
Property prop = _primary.get(name);
if(prop != null) {
@@ -69,23 +73,28 @@ public class MultiValueColumnPropertyMap implements PropertyMap
return _complex.get(name);
}
+ @Override
public Object getValue(String name) {
return getValue(name, null);
}
+ @Override
public Object getValue(String name, Object defaultValue) {
Property prop = get(name);
return ((prop != null) ? prop.getValue() : defaultValue);
}
+ @Override
public Property put(String name, Object value) {
return put(name, null, value, false);
}
+ @Override
public Property put(String name, DataType type, Object value) {
return put(name, type, value, false);
}
+ @Override
public Property put(String name, DataType type, Object value, boolean isDdl) {
// the only property which seems to go in the "primary" is the "multi
// value" property
@@ -95,6 +104,7 @@ public class MultiValueColumnPropertyMap implements PropertyMap
return _complex.put(name, type, value, isDdl);
}
+ @Override
public void putAll(Iterable<? extends Property> props) {
if(props == null) {
return;
@@ -109,6 +119,7 @@ public class MultiValueColumnPropertyMap implements PropertyMap
}
}
+ @Override
public Property remove(String name) {
if(isPrimaryKey(name)) {
return _primary.remove(name);
@@ -116,11 +127,13 @@ public class MultiValueColumnPropertyMap implements PropertyMap
return _complex.remove(name);
}
+ @Override
public void save() throws IOException {
_primary.save();
_complex.save();
}
+ @Override
public Iterator<Property> iterator() {
final List<Iterator<Property>> iters = new ArrayList<Iterator<Property>>(2);
iters.add(_primary.iterator());
@@ -142,10 +155,12 @@ public class MultiValueColumnPropertyMap implements PropertyMap
return null;
}
+ @Override
public boolean hasNext() {
return (_next != null);
}
+ @Override
public Property next() {
if(!hasNext()) {
throw new NoSuchElementException();
@@ -155,6 +170,7 @@ public class MultiValueColumnPropertyMap implements PropertyMap
return prop;
}
+ @Override
public void remove() {
if(_cur != null) {
_cur.remove();
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java
index a7a5dac..ce729a1 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java
@@ -108,22 +108,27 @@ public class UnsupportedColumnInfoImpl
_values = values;
}
+ @Override
public Map<String,Object> getValues() {
return _values;
}
+ @Override
public Object get(String columnName) {
return getValues().get(columnName);
}
+ @Override
public void set(String columnName, Object value) {
getValues().put(columnName, value);
}
+ @Override
public void update() throws IOException {
getComplexValueForeignKey().updateUnsupportedValue(this);
}
+ @Override
public void delete() throws IOException {
getComplexValueForeignKey().deleteUnsupportedValue(this);
}
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..944e2d3 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,60 @@ 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;
}
-
+
+ @Override
public String getValue() {
return _value;
}
+ @Override
public Date getModifiedDate() {
+ return (Date)_modifiedDate;
+ }
+
+ @Override
+ public LocalDateTime getModifiedLocalDate() {
+ return (LocalDateTime)_modifiedDate;
+ }
+
+ @Override
+ public Object getModifiedDateObject() {
return _modifiedDate;
- }
-
+ }
+
+ @Override
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 +214,22 @@ 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);
+ }
+
+ @Override
public void update() throws IOException {
throw new UnsupportedOperationException(
"This column does not support value updates");
}
-
+
+ @Override
public void delete() throws IOException {
throw new UnsupportedOperationException(
"This column does not support value deletes");
@@ -214,8 +239,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..8c2b9de 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;
@@ -40,42 +40,52 @@ public abstract class BaseDelayedValue implements Value
return _val;
}
+ @Override
public boolean isNull() {
return(getType() == Type.NULL);
}
+ @Override
public Value.Type getType() {
return getDelegate().getType();
}
+ @Override
public Object get() {
return getDelegate().get();
}
+ @Override
public boolean getAsBoolean(LocaleContext ctx) {
return getDelegate().getAsBoolean(ctx);
}
+ @Override
public String getAsString(LocaleContext ctx) {
return getDelegate().getAsString(ctx);
}
- public Date getAsDateTime(LocaleContext ctx) {
- return getDelegate().getAsDateTime(ctx);
+ @Override
+ public LocalDateTime getAsLocalDateTime(LocaleContext ctx) {
+ return getDelegate().getAsLocalDateTime(ctx);
}
+ @Override
public Value getAsDateTimeValue(LocaleContext ctx) {
return getDelegate().getAsDateTimeValue(ctx);
}
+ @Override
public Integer getAsLongInt(LocaleContext ctx) {
return getDelegate().getAsLongInt(ctx);
}
+ @Override
public Double getAsDouble(LocaleContext ctx) {
return getDelegate().getAsDouble(ctx);
}
+ @Override
public BigDecimal getAsBigDecimal(LocaleContext ctx) {
return getDelegate().getAsBigDecimal(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..e2a7453 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;
/**
*
@@ -29,34 +29,42 @@ import com.healthmarketscience.jackcess.expr.LocaleContext;
*/
public abstract class BaseValue implements Value
{
+ @Override
public boolean isNull() {
return(getType() == Type.NULL);
}
+ @Override
public boolean getAsBoolean(LocaleContext ctx) {
throw invalidConversion(Type.LONG);
}
+ @Override
public String getAsString(LocaleContext ctx) {
throw invalidConversion(Type.STRING);
}
- public Date getAsDateTime(LocaleContext ctx) {
- return (Date)getAsDateTimeValue(ctx).get();
+ @Override
+ public LocalDateTime getAsLocalDateTime(LocaleContext ctx) {
+ return (LocalDateTime)getAsDateTimeValue(ctx).get();
}
+ @Override
public Value getAsDateTimeValue(LocaleContext ctx) {
throw invalidConversion(Type.DATE_TIME);
}
+ @Override
public Integer getAsLongInt(LocaleContext ctx) {
throw invalidConversion(Type.LONG);
}
+ @Override
public Double getAsDouble(LocaleContext ctx) {
throw invalidConversion(Type.DOUBLE);
}
+ @Override
public BigDecimal getAsBigDecimal(LocaleContext ctx) {
throw invalidConversion(Type.BIG_DEC);
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java
index 0e78f90..56bc911 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java
@@ -33,10 +33,12 @@ public class BigDecimalValue extends BaseNumericValue
_val = val;
}
+ @Override
public Type getType() {
return Type.BIG_DEC;
}
+ @Override
public Object get() {
return _val;
}
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..9b58941 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");
}
@@ -40,16 +40,18 @@ public class DateTimeValue extends BaseValue
_val = val;
}
+ @Override
public Type getType() {
return _type;
}
+ @Override
public Object get() {
return _val;
}
protected Double getNumber(LocaleContext ctx) {
- return ColumnImpl.toDateDouble(_val, ctx.getCalendar());
+ return ColumnImpl.toDateDouble(_val);
}
@Override
@@ -64,7 +66,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..31d919b 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);
- }
+ 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..5a871e4 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;
@@ -54,6 +53,7 @@ public class DefaultFunctions
}
public static final FunctionLookup LOOKUP = new FunctionLookup() {
+ @Override
public Function getFunction(String name) {
return FUNCS.get(DatabaseImpl.toLookupName(name));
}
@@ -310,16 +310,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 +349,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/DoubleValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java
index d37c90b..e00e24a 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java
@@ -33,10 +33,12 @@ public class DoubleValue extends BaseNumericValue
_val = val;
}
+ @Override
public Type getType() {
return Type.DOUBLE;
}
+ @Override
public Object get() {
return _val;
}
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 8705490..39f7050 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;
@@ -247,23 +239,29 @@ class ExpressionTokenizer
}
private static String parseQuotedString(ExprBuf buf, char quoteChar) {
- return parseStringUntil(buf, quoteChar, null, true);
+ return parseStringUntil(buf, null, quoteChar, true);
}
private static String parseObjNameString(ExprBuf buf) {
- return parseStringUntil(buf, OBJ_NAME_END_CHAR, OBJ_NAME_START_CHAR, false);
+ return parseStringUntil(buf, OBJ_NAME_START_CHAR, OBJ_NAME_END_CHAR, false);
}
private static String parseDateLiteralString(ExprBuf buf) {
- return parseStringUntil(buf, DATE_LIT_QUOTE_CHAR, null, false);
+ return parseStringUntil(buf, null, DATE_LIT_QUOTE_CHAR, false);
}
- private static String parseStringUntil(ExprBuf buf, char endChar,
- Character startChar,
- boolean allowDoubledEscape)
+ static String parseStringUntil(ExprBuf buf, Character startChar,
+ char endChar, boolean allowDoubledEscape)
{
- StringBuilder sb = buf.getScratchBuffer();
+ return parseStringUntil(buf, startChar, endChar, allowDoubledEscape,
+ buf.getScratchBuffer())
+ .toString();
+ }
+ static StringBuilder parseStringUntil(
+ ExprBuf buf, Character startChar, char endChar, boolean allowDoubledEscape,
+ StringBuilder sb)
+ {
boolean complete = false;
while(buf.hasNext()) {
char c = buf.next();
@@ -288,7 +286,7 @@ class ExpressionTokenizer
"' for quoted string " + buf);
}
- return sb.toString();
+ return sb;
}
private static Token parseDateLiteral(ExprBuf buf)
@@ -304,14 +302,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);
}
}
@@ -324,7 +334,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));
@@ -351,23 +361,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);
@@ -463,32 +456,17 @@ 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
+ 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) {
+ ExprBuf(String str, ParseContext ctx) {
_str = str;
_ctx = ctx;
}
@@ -537,10 +515,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;
@@ -604,98 +583,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..d9cf5b9 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:
@@ -1690,22 +1685,27 @@ public class Expressionator
return areConstant(_left, _right);
}
+ @Override
public OpType getOp() {
return _op;
}
+ @Override
public Expr getLeft() {
return _left;
}
+ @Override
public void setLeft(Expr left) {
_left = left;
}
+ @Override
public Expr getRight() {
return _right;
}
+ @Override
public void setRight(Expr right) {
_right = right;
}
@@ -1753,14 +1753,17 @@ public class Expressionator
return _expr.isConstant();
}
+ @Override
public OpType getOp() {
return _op;
}
+ @Override
public Expr getRight() {
return _expr;
}
+ @Override
public void setRight(Expr right) {
_expr = right;
}
@@ -1859,14 +1862,17 @@ public class Expressionator
return _expr.isConstant();
}
+ @Override
public OpType getOp() {
return _op;
}
+ @Override
public Expr getLeft() {
return _expr;
}
+ @Override
public void setLeft(Expr left) {
_expr = left;
}
@@ -1991,10 +1997,12 @@ public class Expressionator
return _expr.isConstant() && areConstant(_startRangeExpr, _endRangeExpr);
}
+ @Override
public Expr getRight() {
return _endRangeExpr;
}
+ @Override
public void setRight(Expr right) {
_endRangeExpr = right;
}
@@ -2037,22 +2045,27 @@ public class Expressionator
_expr = expr;
}
+ @Override
public String toDebugString(LocaleContext ctx) {
return _expr.toDebugString(ctx);
}
+ @Override
public String toRawString() {
return _rawExprStr;
}
+ @Override
public String toCleanString(LocaleContext ctx) {
return _expr.toCleanString(ctx);
}
+ @Override
public boolean isConstant() {
return _expr.isConstant();
}
+ @Override
public void collectIdentifiers(Collection<Identifier> identifiers) {
_expr.collectIdentifiers(identifiers);
}
@@ -2081,7 +2094,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:
@@ -2117,6 +2130,7 @@ public class Expressionator
_resultType = resultType;
}
+ @Override
public Object eval(EvalContext ctx) {
return evalValue(_resultType, ctx);
}
@@ -2132,6 +2146,7 @@ public class Expressionator
super(rawExprStr, expr);
}
+ @Override
public Object eval(EvalContext ctx) {
return evalCondition(ctx);
}
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..6dfc2e3 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java
@@ -17,16 +17,31 @@ 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.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalField;
+import java.time.temporal.WeekFields;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
import com.healthmarketscience.jackcess.expr.EvalContext;
+import com.healthmarketscience.jackcess.expr.EvalException;
import com.healthmarketscience.jackcess.expr.NumericConfig;
import com.healthmarketscience.jackcess.expr.TemporalConfig;
import com.healthmarketscience.jackcess.expr.Value;
+import static com.healthmarketscience.jackcess.impl.expr.ExpressionTokenizer.ExprBuf;
/**
*
@@ -35,10 +50,21 @@ import com.healthmarketscience.jackcess.expr.Value;
public class FormatUtil
{
public enum NumPatternType {
- GENERAL, CURRENCY {
+ GENERAL,
+ CURRENCY {
@Override
protected void appendPrefix(StringBuilder fmt) {
- fmt.append("\u00A4");
+ fmt.append('\u00A4');
+ }
+ @Override
+ protected boolean useParensForNegatives(NumericConfig cfg) {
+ return cfg.useParensForCurrencyNegatives();
+ }
+ },
+ EURO {
+ @Override
+ protected void appendPrefix(StringBuilder fmt) {
+ fmt.append('\u20AC');
}
@Override
protected boolean useParensForNegatives(NumericConfig cfg) {
@@ -48,7 +74,7 @@ public class FormatUtil
PERCENT {
@Override
protected void appendSuffix(StringBuilder fmt) {
- fmt.append("%");
+ fmt.append('%');
}
},
SCIENTIFIC {
@@ -67,10 +93,29 @@ public class FormatUtil
}
}
+ private enum TextCase {
+ NONE,
+ UPPER {
+ @Override public char apply(char c) {
+ return Character.toUpperCase(c);
+ }
+ },
+ LOWER {
+ @Override public char apply(char c) {
+ return Character.toLowerCase(c);
+ }
+ };
+
+ public char apply(char c) {
+ return c;
+ }
+ }
+
private static final Map<String,Fmt> PREDEF_FMTS = new HashMap<String,Fmt>();
static {
- PREDEF_FMTS.put("General Date", new GenPredefDateFmt());
+ PREDEF_FMTS.put("General Date", args -> ValueSupport.toValue(
+ args.coerceToDateTimeValue().getAsString()));
PREDEF_FMTS.put("Long Date",
new PredefDateFmt(TemporalConfig.Type.LONG_DATE));
PREDEF_FMTS.put("Medium Date",
@@ -84,12 +129,11 @@ public class FormatUtil
PREDEF_FMTS.put("Short Time",
new PredefDateFmt(TemporalConfig.Type.SHORT_TIME));
- PREDEF_FMTS.put("General Number", new GenPredefNumberFmt());
+ PREDEF_FMTS.put("General Number", args -> ValueSupport.toValue(
+ args.coerceToNumberValue().getAsString()));
PREDEF_FMTS.put("Currency",
new PredefNumberFmt(NumericConfig.Type.CURRENCY));
- // FIXME ?
- // PREDEF_FMTS.put("Euro",
- // new PredefNumberFmt(???));
+ PREDEF_FMTS.put("Euro", new PredefNumberFmt(NumericConfig.Type.EURO));
PREDEF_FMTS.put("Fixed",
new PredefNumberFmt(NumericConfig.Type.FIXED));
PREDEF_FMTS.put("Standard",
@@ -103,23 +147,966 @@ public class FormatUtil
PREDEF_FMTS.put("On/Off", new PredefBoolFmt("On", "Off"));
}
+ private static final Fmt NULL_FMT = args -> ValueSupport.EMPTY_STR_VAL;
+
+ private static final char QUOTE_CHAR = '"';
+ private static final char ESCAPE_CHAR = '\\';
+ private static final char LEFT_ALIGN_CHAR = '!';
+ private static final char START_COLOR_CHAR = '[';
+ private static final char END_COLOR_CHAR = ']';
+ private static final char CHOICE_SEP_CHAR = ';';
+ // this only seems to be useful if you have fixed length string fields which
+ // isn't a normal thing in ms access
+ private static final char FILL_ESCAPE_CHAR = '*';
+ private static final char REQ_PLACEHOLDER_CHAR = '@';
+ private static final char OPT_PLACEHOLDER_CHAR = '&';
+ private static final char TO_UPPER_CHAR = '>';
+ private static final char TO_LOWER_CHAR = '<';
+ private static final char DT_LIT_COLON_CHAR = ':';
+ private static final char DT_LIT_SLASH_CHAR = '/';
+ private static final char SINGLE_QUOTE_CHAR = '\'';
+ private static final char EXP_E_CHAR = 'E';
+ private static final char EXP_e_CHAR = 'e';
+ private static final char PLUS_CHAR = '+';
+ private static final char MINUS_CHAR = '-';
+ private static final char REQ_DIGIT_CHAR = '0';
+ private static final int NO_CHAR = -1;
+
+ private static final byte FCT_UNKNOWN = 0;
+ private static final byte FCT_LITERAL = 1;
+ private static final byte FCT_GENERAL = 2;
+ private static final byte FCT_DATE = 3;
+ private static final byte FCT_NUMBER = 4;
+ private static final byte FCT_TEXT = 5;
+
+ private static final byte[] FORMAT_CODE_TYPES = new byte[127];
+ static {
+ setFormatCodeTypes(" $+-()", FCT_LITERAL);
+ setFormatCodeTypes("\"!*\\[];", FCT_GENERAL);
+ setFormatCodeTypes(":/cdwmqyhnstampmAMPM", FCT_DATE);
+ setFormatCodeTypes(".,0#%Ee", FCT_NUMBER);
+ setFormatCodeTypes("@&<>", FCT_TEXT);
+ }
+
+ @FunctionalInterface
+ interface Fmt {
+ public Value format(Args args);
+ }
+
+ @FunctionalInterface
+ interface DateFormatBuilder {
+ public void build(DateTimeFormatterBuilder dtfb, Args args,
+ boolean hasAmPm, Value.Type dtType);
+ }
+
+ private static final DateFormatBuilder PARTIAL_PREFIX =
+ (dtfb, args, hasAmPm, dtType) -> {
+ throw new UnsupportedOperationException();
+ };
+
+ private static final Map<String,DateFormatBuilder> DATE_FMT_BUILDERS =
+ new HashMap<>();
+ static {
+ DATE_FMT_BUILDERS.put("c",
+ (dtfb, args, hasAmPm, dtType) ->
+ dtfb.append(ValueSupport.getDateFormatForType(
+ args._ctx, dtType)));
+ DATE_FMT_BUILDERS.put("d", new SimpleDFB("d"));
+ DATE_FMT_BUILDERS.put("dd", new SimpleDFB("dd"));
+ DATE_FMT_BUILDERS.put("ddd", new SimpleDFB("eee"));
+ DATE_FMT_BUILDERS.put("dddd", new SimpleDFB("eeee"));
+ DATE_FMT_BUILDERS.put("ddddd", new PredefDFB(TemporalConfig.Type.SHORT_DATE));
+ DATE_FMT_BUILDERS.put("dddddd", new PredefDFB(TemporalConfig.Type.LONG_DATE));
+ DATE_FMT_BUILDERS.put("w", new WeekBasedDFB() {
+ @Override
+ protected TemporalField getField(WeekFields weekFields) {
+ return weekFields.dayOfWeek();
+ }
+ });
+ DATE_FMT_BUILDERS.put("ww", new WeekBasedDFB() {
+ @Override
+ protected TemporalField getField(WeekFields weekFields) {
+ return weekFields.weekOfWeekBasedYear();
+ }
+ });
+ DATE_FMT_BUILDERS.put("m", new SimpleDFB("L"));
+ DATE_FMT_BUILDERS.put("mm", new SimpleDFB("LL"));
+ DATE_FMT_BUILDERS.put("mmm", new SimpleDFB("MMM"));
+ DATE_FMT_BUILDERS.put("mmmm", new SimpleDFB("MMMM"));
+ DATE_FMT_BUILDERS.put("q", new SimpleDFB("Q"));
+ DATE_FMT_BUILDERS.put("y", new SimpleDFB("D"));
+ DATE_FMT_BUILDERS.put("yy", new SimpleDFB("yy"));
+ DATE_FMT_BUILDERS.put("yyyy", new SimpleDFB("yyyy"));
+ DATE_FMT_BUILDERS.put("h", new HourlyDFB("h", "H"));
+ DATE_FMT_BUILDERS.put("hh", new HourlyDFB("hh", "HH"));
+ DATE_FMT_BUILDERS.put("n", new SimpleDFB("m"));
+ DATE_FMT_BUILDERS.put("nn", new SimpleDFB("mm"));
+ DATE_FMT_BUILDERS.put("s", new SimpleDFB("s"));
+ DATE_FMT_BUILDERS.put("ss", new SimpleDFB("ss"));
+ DATE_FMT_BUILDERS.put("ttttt", new PredefDFB(TemporalConfig.Type.LONG_TIME));
+ DATE_FMT_BUILDERS.put("AM/PM", new AmPmDFB("AM", "PM"));
+ DATE_FMT_BUILDERS.put("am/pm", new AmPmDFB("am", "pm"));
+ DATE_FMT_BUILDERS.put("A/P", new AmPmDFB("A", "P"));
+ DATE_FMT_BUILDERS.put("a/p", new AmPmDFB("a", "p"));
+ DATE_FMT_BUILDERS.put("AMPM",
+ (dtfb, args, hasAmPm, dtType) -> {
+ String[] amPmStrs = args._ctx.getTemporalConfig().getAmPmStrings();
+ new AmPmDFB(amPmStrs[0], amPmStrs[1]).build(dtfb, args, hasAmPm, dtType);
+ }
+ );
+ fillInPartialPrefixes();
+ }
+
+ private static final int NF_POS_IDX = 0;
+ private static final int NF_NEG_IDX = 1;
+ private static final int NF_ZERO_IDX = 2;
+ private static final int NF_NULL_IDX = 3;
+ private static final int NUM_NF_FMTS = 4;
+
+ private static final NumberFormatter.NotationType[] NO_EXP_TYPES =
+ new NumberFormatter.NotationType[NUM_NF_FMTS];
+ private static final boolean[] NO_FMT_TYPES = new boolean[NUM_NF_FMTS];
+
+
+ private static final class Args
+ {
+ private final EvalContext _ctx;
+ private Value _expr;
+ private final int _firstDay;
+ private final int _firstWeekType;
+
+ private Args(EvalContext ctx, Value expr, int firstDay, int firstWeekType) {
+ _ctx = ctx;
+ _expr = expr;
+ _firstDay = firstDay;
+ _firstWeekType = firstWeekType;
+ }
+
+ public boolean isNullOrEmptyString() {
+ return(_expr.isNull() ||
+ // only a string value could ever be an empty string
+ (_expr.getType().isString() && getAsString().isEmpty()));
+ }
+
+ public boolean maybeCoerceToEmptyString() {
+ if(isNullOrEmptyString()) {
+ // ensure that we have a non-null value when formatting (null acts
+ // like empty string)
+ _expr = ValueSupport.EMPTY_STR_VAL;
+ return true;
+ }
+ return false;
+ }
+
+ public Args coerceToDateTimeValue() {
+ if(!_expr.getType().isTemporal()) {
+
+ // format coerces boolean strings to numbers
+ Value boolExpr = null;
+ if(_expr.getType().isString() &&
+ ((boolExpr = maybeGetStringAsBooleanValue()) != null)) {
+ _expr = boolExpr;
+ }
+
+ // StringValue already handles most String -> Number -> Date/Time, so
+ // most other convertions work here (and failures are thrown so that
+ // default handling kicks in)
+ _expr = _expr.getAsDateTimeValue(_ctx);
+ }
+ return this;
+ }
+
+ public Args coerceToNumberValue() {
+ if(!_expr.getType().isNumeric()) {
+ if(_expr.getType().isString()) {
+
+ // format coerces "true"/"false" to boolean values
+ Value boolExpr = maybeGetStringAsBooleanValue();
+ if(boolExpr != null) {
+ _expr = boolExpr;
+ } else {
+ BigDecimal bd = DefaultFunctions.maybeGetAsBigDecimal(_ctx, _expr);
+ if(bd != null) {
+ _expr = ValueSupport.toValue(bd);
+ } else {
+ // convert to date to number. this doesn't happen as part of the
+ // default value coercion behavior, but the format method tries
+ // harder
+ Value maybe = DefaultFunctions.maybeGetAsDateTimeValue(
+ _ctx, _expr);
+ if(maybe != null) {
+ _expr = ValueSupport.toValue(maybe.getAsDouble(_ctx));
+ } else {
+ // string which can't be converted to number force failure
+ // here so default formatting will kick in
+ throw new EvalException("invalid number value");
+ }
+ }
+ }
+ } else {
+ // convert date to number
+ _expr = ValueSupport.toValue(_expr.getAsDouble(_ctx));
+ }
+ }
+ return this;
+ }
+
+ private Value maybeGetStringAsBooleanValue() {
+ // format coerces "true"/"false" to boolean values
+ String val = getAsString();
+ if("true".equalsIgnoreCase(val)) {
+ return ValueSupport.TRUE_VAL;
+ }
+ if("false".equalsIgnoreCase(val)) {
+ return ValueSupport.FALSE_VAL;
+ }
+ return null;
+ }
+
+ public BigDecimal getAsBigDecimal() {
+ coerceToNumberValue();
+ return _expr.getAsBigDecimal(_ctx);
+ }
+
+ public LocalDateTime getAsLocalDateTime() {
+ coerceToDateTimeValue();
+ return _expr.getAsLocalDateTime(_ctx);
+ }
+
+ public boolean getAsBoolean() {
+ // even though string values have a "boolean" value, for formatting,
+ // strings which don't convert to valid boolean/number/date are just
+ // returned as is. so we use coerceToNumberValue to force the exception
+ // to be thrown which results in the "default" formatting behavior.
+ coerceToNumberValue();
+ return _expr.getAsBoolean(_ctx);
+ }
+
+ public String getAsString() {
+ return _expr.getAsString(_ctx);
+ }
+ }
+
private FormatUtil() {}
public static Value format(EvalContext ctx, Value expr, String fmtStr,
int firstDay, int firstWeekType) {
- Fmt predefFmt = PREDEF_FMTS.get(fmtStr);
- if(predefFmt != null) {
- if(expr.isNull()) {
- // predefined formats return null for null
- return ValueSupport.NULL_VAL;
+ try {
+ Args args = new Args(ctx, expr, firstDay, firstWeekType);
+
+ Fmt predefFmt = PREDEF_FMTS.get(fmtStr);
+ if(predefFmt != null) {
+ if(args.isNullOrEmptyString()) {
+ // predefined formats return empty string for null
+ return ValueSupport.EMPTY_STR_VAL;
+ }
+ return predefFmt.format(args);
+ }
+
+ // TODO implement caching for custom formats? put into Bindings. use
+ // special "cache" prefix to know which caches to clear when evalconfig
+ // is altered (could also cache other Format* functions)
+
+ return parseCustomFormat(fmtStr, args).format(args);
+
+ } catch(EvalException ee) {
+ // values which cannot be formatted as the target type are just
+ // returned "as is"
+ return expr;
+ }
+ }
+
+ private static Fmt parseCustomFormat(String fmtStr, Args args) {
+
+ ExprBuf buf = new ExprBuf(fmtStr, null);
+
+ // do partial pass to determine what type of format this is
+ byte curFormatType = determineFormatType(buf);
+
+ // reset buffer for real parse
+ buf.reset(0);
+
+ switch(curFormatType) {
+ case FCT_GENERAL:
+ return parseCustomGeneralFormat(buf, args);
+ case FCT_DATE:
+ return parseCustomDateFormat(buf, args);
+ case FCT_NUMBER:
+ return parseCustomNumberFormat(buf, args);
+ case FCT_TEXT:
+ return parseCustomTextFormat(buf, args);
+ default:
+ throw new EvalException("Invalid format type " + curFormatType);
+ }
+ }
+
+ private static byte determineFormatType(ExprBuf buf) {
+
+ while(buf.hasNext()) {
+ char c = buf.next();
+ byte fmtType = getFormatCodeType(c);
+ switch(fmtType) {
+ case FCT_UNKNOWN:
+ case FCT_LITERAL:
+ // meaningless, ignore for now
+ break;
+ case FCT_GENERAL:
+ switch(c) {
+ case QUOTE_CHAR:
+ parseQuotedString(buf);
+ break;
+ case START_COLOR_CHAR:
+ parseColorString(buf);
+ break;
+ case ESCAPE_CHAR:
+ case FILL_ESCAPE_CHAR:
+ if(buf.hasNext()) {
+ buf.next();
+ }
+ break;
+ default:
+ // meaningless, ignore for now
+ }
+ break;
+ case FCT_DATE:
+ case FCT_NUMBER:
+ case FCT_TEXT:
+ // found specific type
+ return fmtType;
+ default:
+ throw new EvalException("Invalid format type " + fmtType);
+ }
+ }
+
+ // no specific type
+ return FCT_GENERAL;
+ }
+
+ private static Fmt parseCustomGeneralFormat(ExprBuf buf, Args args) {
+
+ // a "general" format is actually a "yes/no" format which functions almost
+ // exactly like a number format (without any number format specific chars)
+
+ StringBuilder sb = new StringBuilder();
+ String[] fmtStrs = new String[NUM_NF_FMTS];
+ int fmtIdx = 0;
+
+ BUF_LOOP:
+ while(buf.hasNext()) {
+ char c = buf.next();
+ int fmtType = getFormatCodeType(c);
+ switch(fmtType) {
+ case FCT_GENERAL:
+ switch(c) {
+ case LEFT_ALIGN_CHAR:
+ // no effect
+ break;
+ case QUOTE_CHAR:
+ parseQuotedString(buf, sb);
+ break;
+ case START_COLOR_CHAR:
+ // color strings seem to be ignored
+ parseColorString(buf);
+ break;
+ case ESCAPE_CHAR:
+ if(buf.hasNext()) {
+ sb.append(buf.next());
+ }
+ break;
+ case FILL_ESCAPE_CHAR:
+ // unclear what this actually does. online examples don't seem to
+ // match with experimental results. for now, ignore
+ if(buf.hasNext()) {
+ buf.next();
+ }
+ break;
+ case CHOICE_SEP_CHAR:
+ // yes/no (number) format supports up to 4 formats: pos, neg, zero,
+ // null. after that, ignore the rest
+ if(fmtIdx == (NUM_NF_FMTS - 1)) {
+ // last possible format, ignore remaining
+ break BUF_LOOP;
+ }
+ addCustomGeneralFormat(fmtStrs, fmtIdx++, sb);
+ break;
+ default:
+ sb.append(c);
+ }
+ break;
+ default:
+ sb.append(c);
+ }
+ }
+
+ // fill in remaining formats
+ while(fmtIdx < NUM_NF_FMTS) {
+ addCustomGeneralFormat(fmtStrs, fmtIdx++, sb);
+ }
+
+ return new CustomGeneralFmt(
+ ValueSupport.toValue(fmtStrs[NF_POS_IDX]),
+ ValueSupport.toValue(fmtStrs[NF_NEG_IDX]),
+ ValueSupport.toValue(fmtStrs[NF_ZERO_IDX]),
+ ValueSupport.toValue(fmtStrs[NF_NULL_IDX]));
+ }
+
+ private static void addCustomGeneralFormat(String[] fmtStrs, int fmtIdx,
+ StringBuilder sb)
+ {
+ addCustomNumberFormat(fmtStrs, NO_EXP_TYPES, NO_FMT_TYPES, NO_FMT_TYPES,
+ fmtIdx, sb);
+ }
+
+ private static Fmt parseCustomDateFormat(ExprBuf buf, Args args) {
+
+ // keep track of some extra state while parsing the format, whether or not
+ // there was an am/pm pattern and whether or not there was a general
+ // date/time pattern
+ boolean[] fmtState = new boolean[]{false, false};
+ List<DateFormatBuilder> dfbs = new ArrayList<>();
+
+ BUF_LOOP:
+ while(buf.hasNext()) {
+ char c = buf.next();
+ int fmtType = getFormatCodeType(c);
+ switch(fmtType) {
+ case FCT_GENERAL:
+ switch(c) {
+ case QUOTE_CHAR:
+ String str = parseQuotedString(buf);
+ dfbs.add((dtfb, argsParam, hasAmPmParam, dtType) ->
+ dtfb.appendLiteral(str));
+ break;
+ case START_COLOR_CHAR:
+ // color strings seem to be ignored
+ parseColorString(buf);
+ break;
+ case ESCAPE_CHAR:
+ if(buf.hasNext()) {
+ dfbs.add(buildLiteralCharDFB(buf.next()));
+ }
+ break;
+ case FILL_ESCAPE_CHAR:
+ // unclear what this actually does. online examples don't seem to
+ // match with experimental results. for now, ignore
+ if(buf.hasNext()) {
+ buf.next();
+ }
+ break;
+ case CHOICE_SEP_CHAR:
+ // date/time doesn't use multiple pattern choices, but it does
+ // respect the char. ignore everything after the first choice
+ break BUF_LOOP;
+ default:
+ dfbs.add(buildLiteralCharDFB(c));
+ }
+ break;
+ case FCT_DATE:
+ parseCustomDateFormatPattern(c, buf, dfbs, fmtState, args);
+ break;
+ default:
+ dfbs.add(buildLiteralCharDFB(c));
+ }
+ }
+
+ boolean hasAmPm = fmtState[0];
+ boolean hasGeneralFormat = fmtState[1];
+ if(!hasGeneralFormat) {
+ // simple situation, one format for every value
+ DateTimeFormatter dtf = createDateTimeFormatter(dfbs, args, hasAmPm, null);
+ return new CustomFmt(argsParam -> ValueSupport.toValue(
+ dtf.format(argsParam.getAsLocalDateTime())));
+ }
+
+ // we need separate formatters for date, time, and date/time values
+ DateTimeFormatter dateFmt = createDateTimeFormatter(dfbs, args, hasAmPm,
+ Value.Type.DATE);
+ DateTimeFormatter timeFmt = createDateTimeFormatter(dfbs, args, hasAmPm,
+ Value.Type.TIME);
+ DateTimeFormatter dtFmt = createDateTimeFormatter(dfbs, args, hasAmPm,
+ Value.Type.DATE_TIME);
+
+ return new CustomFmt(argsParam -> formatDateTime(
+ argsParam, dateFmt, timeFmt, dtFmt));
+ }
+
+ private static void parseCustomDateFormatPattern(
+ char c, ExprBuf buf, List<DateFormatBuilder> dfbs,
+ boolean[] fmtState, Args args) {
+
+ if((c == DT_LIT_COLON_CHAR) || (c == DT_LIT_SLASH_CHAR)) {
+ // date/time literal char, nothing more to do
+ dfbs.add(buildLiteralCharDFB(c));
+ return;
+ }
+
+ StringBuilder sb = buf.getScratchBuffer();
+ sb.append(c);
+
+ char firstChar = c;
+ int firstPos = buf.curPos();
+ String bestMatchPat = sb.toString();
+
+ DateFormatBuilder bestMatch = DATE_FMT_BUILDERS.get(bestMatchPat);
+ int bestPos = firstPos;
+ while(buf.hasNext()) {
+ sb.append(buf.next());
+ String tmpPat = sb.toString();
+ DateFormatBuilder dfb = DATE_FMT_BUILDERS.get(tmpPat);
+ if(dfb == null) {
+ // no more possible matches
+ break;
+ }
+ if(dfb != PARTIAL_PREFIX) {
+ // this is the longest, valid pattern we have seen so far
+ bestMatch = dfb;
+ bestPos = buf.curPos();
+ bestMatchPat = tmpPat;
+ }
+ }
+
+ if(bestMatch != PARTIAL_PREFIX) {
+
+ // apply valid pattern
+ buf.reset(bestPos);
+ dfbs.add(bestMatch);
+
+ switch(firstChar) {
+ case 'a':
+ case 'A':
+ // this was an am/pm pattern
+ fmtState[0] = true;
+ break;
+ case 'c':
+ // this was a general date/time format
+ fmtState[1] = true;
+ break;
+ default:
+ // don't care
+ }
+
+ } else {
+
+ // just consume the first char
+ buf.reset(firstPos);
+ dfbs.add(buildLiteralCharDFB(firstChar));
+ }
+ }
+
+ private static DateFormatBuilder buildLiteralCharDFB(char c) {
+ return (dtfb, args, hasAmPm, dtType) -> dtfb.appendLiteral(c);
+ }
+
+ private static DateTimeFormatter createDateTimeFormatter(
+ List<DateFormatBuilder> dfbs, Args args, boolean hasAmPm,
+ Value.Type dtType)
+ {
+ DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
+ dfbs.forEach(d -> d.build(dtfb, args, hasAmPm, dtType));
+ return dtfb.toFormatter(args._ctx.getTemporalConfig().getLocale());
+ }
+
+ private static Value formatDateTime(
+ Args args, DateTimeFormatter dateFmt,
+ DateTimeFormatter timeFmt, DateTimeFormatter dtFmt)
+ {
+ LocalDateTime ldt = args.getAsLocalDateTime();
+ DateTimeFormatter fmt = null;
+ switch(args._expr.getType()) {
+ case DATE:
+ fmt = dateFmt;
+ break;
+ case TIME:
+ fmt = timeFmt;
+ break;
+ default:
+ fmt = dtFmt;
+ }
+
+ return ValueSupport.toValue(fmt.format(ldt));
+ }
+
+ private static Fmt parseCustomNumberFormat(ExprBuf buf, Args args) {
+
+ StringBuilder sb = new StringBuilder();
+ String[] fmtStrs = new String[NUM_NF_FMTS];
+ int fmtIdx = 0;
+ StringBuilder pendingLiteral = new StringBuilder();
+ NumberFormatter.NotationType[] expTypes =
+ new NumberFormatter.NotationType[NUM_NF_FMTS];
+ boolean[] hasFmts = new boolean[NUM_NF_FMTS];
+ boolean[] hasReqDigit = new boolean[NUM_NF_FMTS];
+
+ BUF_LOOP:
+ while(buf.hasNext()) {
+ char c = buf.next();
+ int fmtType = getFormatCodeType(c);
+ switch(fmtType) {
+ case FCT_GENERAL:
+ switch(c) {
+ case LEFT_ALIGN_CHAR:
+ // no effect
+ break;
+ case QUOTE_CHAR:
+ parseQuotedString(buf, pendingLiteral);
+ break;
+ case START_COLOR_CHAR:
+ // color strings seem to be ignored
+ parseColorString(buf);
+ break;
+ case ESCAPE_CHAR:
+ if(buf.hasNext()) {
+ pendingLiteral.append(buf.next());
+ }
+ break;
+ case FILL_ESCAPE_CHAR:
+ // unclear what this actually does. online examples don't seem to
+ // match with experimental results. for now, ignore
+ if(buf.hasNext()) {
+ buf.next();
+ }
+ break;
+ case CHOICE_SEP_CHAR:
+ // number format supports up to 4 formats: pos, neg, zero, null.
+ // after that, ignore the rest
+ if(fmtIdx == (NUM_NF_FMTS - 1)) {
+ // last possible format, ignore remaining
+ break BUF_LOOP;
+ }
+ flushPendingNumberLiteral(pendingLiteral, sb);
+ addCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
+ fmtIdx++, sb);
+ break;
+ default:
+ pendingLiteral.append(c);
+ }
+ break;
+ case FCT_NUMBER:
+ hasFmts[fmtIdx] = true;
+ switch(c) {
+ case EXP_E_CHAR:
+ int signChar = buf.peekNext();
+ if((signChar == PLUS_CHAR) || (signChar == MINUS_CHAR)) {
+ buf.next();
+ expTypes[fmtIdx] = ((signChar == PLUS_CHAR) ?
+ NumberFormatter.NotationType.EXP_E_PLUS :
+ NumberFormatter.NotationType.EXP_E_MINUS);
+ flushPendingNumberLiteral(pendingLiteral, sb);
+ sb.append(EXP_E_CHAR);
+ } else {
+ pendingLiteral.append(c);
+ }
+ break;
+ case EXP_e_CHAR:
+ signChar = buf.peekNext();
+ if((signChar == PLUS_CHAR) || (signChar == MINUS_CHAR)) {
+ buf.next();
+ expTypes[fmtIdx] = ((signChar == PLUS_CHAR) ?
+ NumberFormatter.NotationType.EXP_e_PLUS :
+ NumberFormatter.NotationType.EXP_e_MINUS);
+ flushPendingNumberLiteral(pendingLiteral, sb);
+ sb.append(EXP_E_CHAR);
+ } else {
+ pendingLiteral.append(c);
+ }
+ break;
+ case REQ_DIGIT_CHAR:
+ hasReqDigit[fmtIdx] = true;
+ flushPendingNumberLiteral(pendingLiteral, sb);
+ sb.append(c);
+ break;
+ default:
+ // most number format chars pass straight through
+ flushPendingNumberLiteral(pendingLiteral, sb);
+ sb.append(c);
+ }
+ break;
+ default:
+ pendingLiteral.append(c);
+ }
+ }
+
+ // fill in remaining formats
+ while(fmtIdx < NUM_NF_FMTS) {
+ flushPendingNumberLiteral(pendingLiteral, sb);
+ addCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
+ fmtIdx++, sb);
+ }
+
+ return new CustomNumberFmt(
+ createCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
+ NF_POS_IDX, false, args, buf),
+ createCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
+ NF_NEG_IDX, false, args, buf),
+ createCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
+ NF_ZERO_IDX, true, args, buf),
+ createCustomNumberFormat(fmtStrs, expTypes, hasFmts, hasReqDigit,
+ NF_NULL_IDX, true, args, buf));
+ }
+
+ private static void addCustomNumberFormat(
+ String[] fmtStrs, NumberFormatter.NotationType[] expTypes,
+ boolean[] hasFmts, boolean[] hasReqDigit, int fmtIdx, StringBuilder sb)
+ {
+ if(sb.length() == 0) {
+ // do special empty format handling on a per-format-type basis
+ switch(fmtIdx) {
+ case NF_NEG_IDX:
+ // re-use "pos" format
+ sb.append('-').append(fmtStrs[NF_POS_IDX]);
+ expTypes[NF_NEG_IDX] = expTypes[NF_POS_IDX];
+ hasFmts[NF_NEG_IDX] = hasFmts[NF_POS_IDX];
+ hasReqDigit[NF_NEG_IDX] = hasReqDigit[NF_POS_IDX];
+ break;
+ case NF_ZERO_IDX:
+ // re-use "pos" format
+ sb.append(fmtStrs[NF_POS_IDX]);
+ expTypes[NF_ZERO_IDX] = expTypes[NF_POS_IDX];
+ hasFmts[NF_ZERO_IDX] = hasFmts[NF_POS_IDX];
+ hasReqDigit[NF_ZERO_IDX] = hasReqDigit[NF_POS_IDX];
+ break;
+ default:
+ // use empty string result
+ }
+ }
+
+ fmtStrs[fmtIdx] = sb.toString();
+ sb.setLength(0);
+ }
+
+ private static void flushPendingNumberLiteral(
+ StringBuilder pendingLiteral, StringBuilder sb) {
+ if(pendingLiteral.length() == 0) {
+ return;
+ }
+
+ if((pendingLiteral.length() == 1) &&
+ pendingLiteral.charAt(0) == SINGLE_QUOTE_CHAR) {
+ // handle standalone single quote
+ sb.append(SINGLE_QUOTE_CHAR).append(SINGLE_QUOTE_CHAR);
+ pendingLiteral.setLength(0);
+ return;
+ }
+
+ sb.append(SINGLE_QUOTE_CHAR);
+ int startPos = sb.length();
+ sb.append(pendingLiteral);
+
+ // we need to quote any single quotes in the literal string
+ for(int i = startPos; i < sb.length(); ++i) {
+ char c = sb.charAt(i);
+ if(c == SINGLE_QUOTE_CHAR) {
+ sb.insert(++i, SINGLE_QUOTE_CHAR);
+ }
+ }
+
+ sb.append(SINGLE_QUOTE_CHAR);
+ pendingLiteral.setLength(0);
+ }
+
+ private static BDFormat createCustomNumberFormat(
+ String[] fmtStrs, NumberFormatter.NotationType[] expTypes,
+ boolean[] hasFmts, boolean[] hasReqDigit, int fmtIdx,
+ boolean isZeroFmt, Args args, ExprBuf buf) {
+
+ String fmtStr = fmtStrs[fmtIdx];
+ if(!hasFmts[fmtIdx]) {
+ // convert the literal string to a dummy number format
+ if(fmtStr.length() > 0) {
+ // strip quoting
+ StringBuilder sb = buf.getScratchBuffer().append(fmtStr)
+ .deleteCharAt(fmtStr.length() - 1)
+ .deleteCharAt(0);
+ if(sb.length() > 0) {
+ for(int i = 0; i < sb.length(); ++i) {
+ if(sb.charAt(i) == SINGLE_QUOTE_CHAR) {
+ // delete next single quote char
+ sb.deleteCharAt(++i);
+ }
+ }
+ } else {
+ // this was a single, literal single quote
+ sb.append(SINGLE_QUOTE_CHAR);
+ }
+ fmtStr = sb.toString();
+ }
+ return new LiteralBDFormat(fmtStr);
+ }
+
+ NumberFormatter.NotationType expType = expTypes[fmtIdx];
+ DecimalFormat df = args._ctx.createDecimalFormat(fmtStr);
+
+ if(df.getMaximumFractionDigits() > 0) {
+ // if the decimal is included in the format, access always shows it
+ df.setDecimalSeparatorAlwaysShown(true);
+ }
+
+ if(expType != null) {
+ NumberFormat nf = new NumberFormatter.ScientificFormat(df, expType);
+ if(isZeroFmt) {
+ return new LiteralBDFormat(nf.format(BigDecimal.ZERO));
+ }
+ return new BaseBDFormat(nf);
+ }
+
+ if(!hasReqDigit[fmtIdx]) {
+ // java likes to force extra 0's while access doesn't
+ df.setMinimumIntegerDigits(0);
+ }
+
+ if(isZeroFmt) {
+
+ String zeroVal = df.format(BigDecimal.ZERO);
+ if(!hasReqDigit[fmtIdx]) {
+ // java forces a 0 but access doesn't. delete any 0 chars which were
+ // inserted by the java format
+ int prefLen = df.getPositivePrefix().length();
+ int len = zeroVal.length() - df.getPositiveSuffix().length();
+ StringBuilder sb = buf.getScratchBuffer().append(zeroVal);
+ for(int i = len - 1; i >= prefLen; --i) {
+ if(sb.charAt(i) == '0') {
+ sb.deleteCharAt(i);
+ }
+ }
+ zeroVal = sb.toString();
+ }
+
+ return new LiteralBDFormat(zeroVal);
+ }
+
+ return new DecimalBDFormat(df);
+ }
+
+ private static Fmt parseCustomTextFormat(ExprBuf buf, Args args) {
+
+ Fmt fmt = null;
+
+ List<BiConsumer<StringBuilder,CharSource>> subFmts = new ArrayList<>();
+ int numPlaceholders = 0;
+ boolean rightAligned = true;
+ TextCase textCase = TextCase.NONE;
+ StringBuilder pendingLiteral = new StringBuilder();
+ boolean hasFmtChars = false;
+
+ BUF_LOOP:
+ while(buf.hasNext()) {
+ char c = buf.next();
+ hasFmtChars = true;
+ int fmtType = getFormatCodeType(c);
+ switch(fmtType) {
+ case FCT_GENERAL:
+ switch(c) {
+ case LEFT_ALIGN_CHAR:
+ rightAligned = false;
+ break;
+ case QUOTE_CHAR:
+ parseQuotedString(buf, pendingLiteral);
+ break;
+ case START_COLOR_CHAR:
+ // color strings seem to be ignored
+ parseColorString(buf);
+ break;
+ case ESCAPE_CHAR:
+ if(buf.hasNext()) {
+ pendingLiteral.append(buf.next());
+ }
+ break;
+ case FILL_ESCAPE_CHAR:
+ // unclear what this actually does. online examples don't seem to
+ // match with experimental results. for now, ignore
+ if(buf.hasNext()) {
+ buf.next();
+ }
+ break;
+ case CHOICE_SEP_CHAR:
+ // text format supports up to 2 formats: normal and empty/null.
+ // after that, ignore the rest
+ if(fmt != null) {
+ // ignore remaining format
+ break BUF_LOOP;
+ }
+ flushPendingTextLiteral(pendingLiteral, subFmts);
+ fmt = new CharSourceFmt(subFmts, numPlaceholders, rightAligned,
+ textCase);
+ // reset for next format
+ subFmts = new ArrayList<>();
+ numPlaceholders = 0;
+ rightAligned = true;
+ textCase = TextCase.NONE;
+ hasFmtChars = false;
+ break;
+ default:
+ pendingLiteral.append(c);
+ }
+ break;
+ case FCT_TEXT:
+ switch(c) {
+ case REQ_PLACEHOLDER_CHAR:
+ flushPendingTextLiteral(pendingLiteral, subFmts);
+ ++numPlaceholders;
+ subFmts.add((sb,cs) -> {
+ int tmp = cs.next();
+ sb.append((tmp != NO_CHAR) ? (char)tmp : ' ');
+ });
+ break;
+ case OPT_PLACEHOLDER_CHAR:
+ flushPendingTextLiteral(pendingLiteral, subFmts);
+ ++numPlaceholders;
+ subFmts.add((sb,cs) -> {
+ int tmp = cs.next();
+ if(tmp != NO_CHAR) {
+ sb.append((char)tmp);
+ }
+ });
+ break;
+ case TO_UPPER_CHAR:
+ // an uppper and lower symbol cancel each other out
+ textCase = ((textCase == TextCase.LOWER) ?
+ TextCase.NONE : TextCase.UPPER);
+ break;
+ case TO_LOWER_CHAR:
+ // an uppper and lower symbol cancel each other out
+ textCase = ((textCase == TextCase.UPPER) ?
+ TextCase.NONE : TextCase.LOWER);
+ break;
+ default:
+ pendingLiteral.append(c);
+ }
+ break;
+ default:
+ pendingLiteral.append(c);
}
- return predefFmt.format(ctx, expr, null, firstDay, firstWeekType);
}
- // FIXME,
- throw new UnsupportedOperationException();
+ flushPendingTextLiteral(pendingLiteral, subFmts);
+
+ Fmt emptyFmt = null;
+ if(fmt == null) {
+ fmt = new CharSourceFmt(subFmts, numPlaceholders, rightAligned,
+ textCase);
+ emptyFmt = NULL_FMT;
+ } else {
+ emptyFmt = (hasFmtChars ?
+ new CharSourceFmt(subFmts, numPlaceholders, rightAligned,
+ textCase) :
+ NULL_FMT);
+ }
+
+ return new CustomFmt(fmt, emptyFmt);
+ }
+
+ private static void flushPendingTextLiteral(
+ StringBuilder pendingLiteral,
+ List<BiConsumer<StringBuilder,CharSource>> subFmts) {
+ if(pendingLiteral.length() == 0) {
+ return;
+ }
+
+ String literal = pendingLiteral.toString();
+ pendingLiteral.setLength(0);
+ subFmts.add((sb, cs) -> sb.append(literal));
}
public static String createNumberFormatPattern(
@@ -153,16 +1140,45 @@ public class FormatUtil
return fmt.toString();
}
+ private static byte getFormatCodeType(char c) {
+ if((c >= 0) && (c < 127)) {
+ return FORMAT_CODE_TYPES[c];
+ }
+ return FCT_UNKNOWN;
+ }
- private static abstract class Fmt
- {
- // FIXME, no null
- // FIXME, need fmtStr?
- public abstract Value format(EvalContext ctx, Value expr, String fmtStr,
- int firstDay, int firstWeekType);
+ private static void setFormatCodeTypes(String chars, byte type) {
+ for(char c : chars.toCharArray()) {
+ FORMAT_CODE_TYPES[c] = type;
+ }
+ }
+
+ private static String parseQuotedString(ExprBuf buf) {
+ return ExpressionTokenizer.parseStringUntil(buf, null, QUOTE_CHAR, true);
+ }
+
+ private static void parseQuotedString(ExprBuf buf, StringBuilder sb) {
+ ExpressionTokenizer.parseStringUntil(buf, null, QUOTE_CHAR, true, sb);
+ }
+
+ private static String parseColorString(ExprBuf buf) {
+ return ExpressionTokenizer.parseStringUntil(
+ buf, START_COLOR_CHAR, END_COLOR_CHAR, false);
+ }
+
+ private static void fillInPartialPrefixes() {
+ List<String> validPrefixes = new ArrayList<>(DATE_FMT_BUILDERS.keySet());
+ for(String validPrefix : validPrefixes) {
+ int len = validPrefix.length();
+ while(len > 1) {
+ --len;
+ validPrefix = validPrefix.substring(0, len);
+ DATE_FMT_BUILDERS.putIfAbsent(validPrefix, PARTIAL_PREFIX);
+ }
+ }
}
- private static class PredefDateFmt extends Fmt
+ private static final class PredefDateFmt implements Fmt
{
private final TemporalConfig.Type _type;
@@ -171,31 +1187,14 @@ public class FormatUtil
}
@Override
- public Value format(EvalContext ctx, Value expr, String fmtStr,
- int firstDay, int firstWeekType) {
- DateFormat sdf = ctx.createDateFormat(
- ctx.getTemporalConfig().getDateTimeFormat(_type));
- return ValueSupport.toValue(sdf.format(expr.getAsDateTime(ctx)));
+ public Value format(Args args) {
+ DateTimeFormatter dtf = args._ctx.createDateFormatter(
+ args._ctx.getTemporalConfig().getDateTimeFormat(_type));
+ return ValueSupport.toValue(dtf.format(args.getAsLocalDateTime()));
}
}
- private static class GenPredefDateFmt extends Fmt
- {
- @Override
- public Value format(EvalContext ctx, Value expr, String fmtStr,
- int firstDay, int firstWeekType) {
- Value tempExpr = expr;
- if(!expr.getType().isTemporal()) {
- Value maybe = DefaultFunctions.maybeGetAsDateTimeValue(ctx, expr);
- if(maybe != null) {
- tempExpr = maybe;
- }
- }
- return ValueSupport.toValue(tempExpr.getAsString(ctx));
- }
- }
-
- private static class PredefBoolFmt extends Fmt
+ private static final class PredefBoolFmt implements Fmt
{
private final Value _trueVal;
private final Value _falseVal;
@@ -206,13 +1205,23 @@ public class FormatUtil
}
@Override
- public Value format(EvalContext ctx, Value expr, String fmtStr,
- int firstDay, int firstWeekType) {
- return(expr.getAsBoolean(ctx) ? _trueVal : _falseVal);
+ public Value format(Args args) {
+ return(args.getAsBoolean() ? _trueVal : _falseVal);
}
}
- private static class PredefNumberFmt extends Fmt
+ private static abstract class BaseNumberFmt implements Fmt
+ {
+ @Override
+ public Value format(Args args) {
+ NumberFormat df = getNumberFormat(args);
+ return ValueSupport.toValue(df.format(args.getAsBigDecimal()));
+ }
+
+ protected abstract NumberFormat getNumberFormat(Args args);
+ }
+
+ private static final class PredefNumberFmt extends BaseNumberFmt
{
private final NumericConfig.Type _type;
@@ -221,51 +1230,381 @@ public class FormatUtil
}
@Override
- public Value format(EvalContext ctx, Value expr, String fmtStr,
- int firstDay, int firstWeekType) {
- DecimalFormat df = ctx.createDecimalFormat(
- ctx.getNumericConfig().getNumberFormat(_type));
- return ValueSupport.toValue(df.format(expr.getAsBigDecimal(ctx)));
+ protected NumberFormat getNumberFormat(Args args) {
+ return args._ctx.createDecimalFormat(
+ args._ctx.getNumericConfig().getNumberFormat(_type));
}
}
- private static class GenPredefNumberFmt extends Fmt
+ private static final class ScientificPredefNumberFmt extends BaseNumberFmt
{
@Override
- public Value format(EvalContext ctx, Value expr, String fmtStr,
- int firstDay, int firstWeekType) {
- Value numExpr = expr;
- if(!expr.getType().isNumeric()) {
- if(expr.getType().isString()) {
- BigDecimal bd = DefaultFunctions.maybeGetAsBigDecimal(ctx, expr);
- if(bd != null) {
- numExpr = ValueSupport.toValue(bd);
- } else {
- // convert to date to number
- Value maybe = DefaultFunctions.maybeGetAsDateTimeValue(ctx, expr);
- if(maybe != null) {
- numExpr = ValueSupport.toValue(maybe.getAsDouble(ctx));
- }
- }
- } else {
- // convert date to number
- numExpr = ValueSupport.toValue(expr.getAsDouble(ctx));
+ protected NumberFormat getNumberFormat(Args args) {
+ NumberFormat df = args._ctx.createDecimalFormat(
+ args._ctx.getNumericConfig().getNumberFormat(
+ NumericConfig.Type.SCIENTIFIC));
+ df = new NumberFormatter.ScientificFormat(df);
+ return df;
+ }
+ }
+
+ private static final class SimpleDFB implements DateFormatBuilder
+ {
+ private final String _pat;
+
+ private SimpleDFB(String pat) {
+ _pat = pat;
+ }
+ @Override
+ public void build(DateTimeFormatterBuilder dtfb, Args args,
+ boolean hasAmPm, Value.Type dtType) {
+ dtfb.appendPattern(_pat);
+ }
+ }
+
+ private static final class HourlyDFB implements DateFormatBuilder
+ {
+ private final String _pat12;
+ private final String _pat24;
+
+ private HourlyDFB(String pat12, String pat24) {
+ _pat12 = pat12;
+ _pat24 = pat24;
+ }
+ @Override
+ public void build(DateTimeFormatterBuilder dtfb, Args args,
+ boolean hasAmPm, Value.Type dtTypePm) {
+ // annoyingly the "hour" patterns are the same and depend on the
+ // existence of the am/pm pattern to determine how they function (12 vs
+ // 24 hour).
+ dtfb.appendPattern(hasAmPm ? _pat12 : _pat24);
+ }
+ }
+
+ private static final class PredefDFB implements DateFormatBuilder
+ {
+ private final TemporalConfig.Type _type;
+
+ private PredefDFB(TemporalConfig.Type type) {
+ _type = type;
+ }
+ @Override
+ public void build(DateTimeFormatterBuilder dtfb, Args args,
+ boolean hasAmPm, Value.Type dtType) {
+ dtfb.appendPattern(args._ctx.getTemporalConfig().getDateTimeFormat(_type));
+ }
+ }
+
+ private static abstract class WeekBasedDFB implements DateFormatBuilder
+ {
+ @Override
+ public void build(DateTimeFormatterBuilder dtfb, Args args,
+ boolean hasAmPm, Value.Type dtType) {
+ dtfb.appendValue(getField(DefaultDateFunctions.weekFields(
+ args._firstDay, args._firstWeekType)));
+ }
+
+ protected abstract TemporalField getField(WeekFields weekFields);
+ }
+
+ private static final class AmPmDFB extends AbstractMap<Long,String>
+ implements DateFormatBuilder
+ {
+ private static final Long ZERO_KEY = 0L;
+ private final String _am;
+ private final String _pm;
+
+ private AmPmDFB(String am, String pm) {
+ _am = am;
+ _pm = pm;
+ }
+ @Override
+ public void build(DateTimeFormatterBuilder dtfb, Args args,
+ boolean hasAmPm, Value.Type dtType) {
+ dtfb.appendText(ChronoField.AMPM_OF_DAY, this);
+ }
+ @Override
+ public int size() {
+ return 2;
+ }
+ @Override
+ public String get(Object key) {
+ return(ZERO_KEY.equals(key) ? _am : _pm);
+ }
+ @Override
+ public Set<Map.Entry<Long,String>> entrySet() {
+ return new AbstractSet<Map.Entry<Long,String>>() {
+ @Override
+ public int size() {
+ return 2;
+ }
+ @Override
+ public Iterator<Map.Entry<Long,String>> iterator() {
+ return Arrays.<Map.Entry<Long,String>>asList(
+ new AbstractMap.SimpleImmutableEntry<Long,String>(0L, _am),
+ new AbstractMap.SimpleImmutableEntry<Long,String>(1L, _pm))
+ .iterator();
}
+ };
+ }
+ }
+
+ private static final class CustomFmt implements Fmt
+ {
+ private final Fmt _fmt;
+ private final Fmt _emptyFmt;
+
+ private CustomFmt(Fmt fmt) {
+ this(fmt, NULL_FMT);
+ }
+
+ private CustomFmt(Fmt fmt, Fmt emptyFmt) {
+ _fmt = fmt;
+ _emptyFmt = emptyFmt;
+ }
+
+ @Override
+ public Value format(Args args) {
+ Fmt fmt = _fmt;
+ if(args.maybeCoerceToEmptyString()) {
+ fmt = _emptyFmt;
}
- return ValueSupport.toValue(numExpr.getAsString(ctx));
+ return fmt.format(args);
}
}
- private static class ScientificPredefNumberFmt extends Fmt
+ private static final class CharSourceFmt implements Fmt
{
+ private final List<BiConsumer<StringBuilder,CharSource>> _subFmts;
+ private final int _numPlaceholders;
+ private final boolean _rightAligned;
+ private final TextCase _textCase;
+
+ private CharSourceFmt(List<BiConsumer<StringBuilder,CharSource>> subFmts,
+ int numPlaceholders, boolean rightAligned,
+ TextCase textCase) {
+ _subFmts = subFmts;
+ _numPlaceholders = numPlaceholders;
+ _rightAligned = rightAligned;
+ _textCase = textCase;
+ }
+
@Override
- public Value format(EvalContext ctx, Value expr, String fmtStr,
- int firstDay, int firstWeekType) {
- NumberFormat df = ctx.createDecimalFormat(
- ctx.getNumericConfig().getNumberFormat(
- NumericConfig.Type.SCIENTIFIC));
- df = new NumberFormatter.ScientificFormat(df);
- return ValueSupport.toValue(df.format(expr.getAsBigDecimal(ctx)));
+ public Value format(Args args) {
+ CharSource cs = new CharSource(args.getAsString(), _numPlaceholders,
+ _rightAligned, _textCase);
+ StringBuilder sb = new StringBuilder();
+ _subFmts.stream().forEach(fmt -> fmt.accept(sb, cs));
+ cs.appendRemaining(sb);
+ return ValueSupport.toValue(sb.toString());
+ }
+ }
+
+ private static final class CharSource
+ {
+ private int _prefLen;
+ private final String _str;
+ private int _strPos;
+ private final TextCase _textCase;
+
+ private CharSource(String str, int len, boolean rightAligned,
+ TextCase textCase) {
+ _str = str;
+ _textCase = textCase;
+ int strLen = str.length();
+ if(len > strLen) {
+ if(rightAligned) {
+ _prefLen = len - strLen;
+ }
+ } else if(len < strLen) {
+ // it doesn't make sense to me, but the meaning of "right aligned"
+ // seems to flip when the string is longer than the format length
+ if(!rightAligned) {
+ _strPos = strLen - len;
+ }
+ }
+ }
+
+ public int next() {
+ if(_prefLen > 0) {
+ --_prefLen;
+ return NO_CHAR;
+ }
+ if(_strPos < _str.length()) {
+ return _textCase.apply(_str.charAt(_strPos++));
+ }
+ return NO_CHAR;
+ }
+
+ public void appendRemaining(StringBuilder sb) {
+ int strLen = _str.length();
+ while(_strPos < strLen) {
+ sb.append(_textCase.apply(_str.charAt(_strPos++)));
+ }
+ }
+ }
+
+ private static abstract class BaseCustomNumberFmt implements Fmt
+ {
+ @Override
+ public Value format(Args args) {
+ if(args._expr.isNull()) {
+ return formatNull(args);
+ }
+
+ BigDecimal bd = args.getAsBigDecimal();
+ int cmp = BigDecimal.ZERO.compareTo(bd);
+
+ return ((cmp < 0) ? formatPos(bd, args) :
+ ((cmp > 0) ? formatNeg(bd, args) :
+ formatZero(bd, args)));
+ }
+
+ protected abstract Value formatNull(Args args);
+ protected abstract Value formatPos(BigDecimal bd, Args args);
+ protected abstract Value formatNeg(BigDecimal bd, Args args);
+ protected abstract Value formatZero(BigDecimal bd, Args args);
+ }
+
+ private static final class CustomGeneralFmt extends BaseCustomNumberFmt
+ {
+ private final Value _posVal;
+ private final Value _negVal;
+ private final Value _zeroVal;
+ private final Value _nullVal;
+
+ private CustomGeneralFmt(Value posVal, Value negVal,
+ Value zeroVal, Value nullVal) {
+ _posVal = posVal;
+ _negVal = negVal;
+ _zeroVal = zeroVal;
+ _nullVal = nullVal;
+ }
+
+ @Override
+ protected Value formatNull(Args args) {
+ return _nullVal;
+ }
+ @Override
+ protected Value formatPos(BigDecimal bd, Args args) {
+ return _posVal;
+ }
+ @Override
+ protected Value formatNeg(BigDecimal bd, Args args) {
+ return _negVal;
+ }
+ @Override
+ protected Value formatZero(BigDecimal bd, Args args) {
+ return _zeroVal;
+ }
+ }
+
+ private static final class CustomNumberFmt extends BaseCustomNumberFmt
+ {
+ private final BDFormat _posFmt;
+ private final BDFormat _negFmt;
+ private final BDFormat _zeroFmt;
+ private final BDFormat _nullFmt;
+
+ private CustomNumberFmt(BDFormat posFmt, BDFormat negFmt,
+ BDFormat zeroFmt, BDFormat nullFmt) {
+ _posFmt = posFmt;
+ _negFmt = negFmt;
+ _zeroFmt = zeroFmt;
+ _nullFmt = nullFmt;
+ }
+
+ private Value formatMaybeZero(BigDecimal bd, BDFormat fmt) {
+ // in theory we want to use the given format. however, if, due to
+ // rounding, we end up with a number equivalent to zero, then we fall
+ // back to the zero format. if we are using scientific notation,
+ // however, then don't worry about this
+ int maxDecDigits = fmt.getMaxDecimalDigits();
+ if(maxDecDigits < bd.scale()) {
+ bd = bd.setScale(maxDecDigits, NumberFormatter.ROUND_MODE);
+ if(BigDecimal.ZERO.compareTo(bd) == 0) {
+ // fall back to zero format
+ fmt = _zeroFmt;
+ }
+ }
+
+ return ValueSupport.toValue(fmt.format(bd));
+ }
+
+ @Override
+ protected Value formatNull(Args args) {
+ return ValueSupport.toValue(_nullFmt.format(BigDecimal.ZERO));
+ }
+ @Override
+ protected Value formatPos(BigDecimal bd, Args args) {
+ return formatMaybeZero(bd, _posFmt);
+ }
+ @Override
+ protected Value formatNeg(BigDecimal bd, Args args) {
+ return formatMaybeZero(bd.negate(), _negFmt);
+ }
+ @Override
+ protected Value formatZero(BigDecimal bd, Args args) {
+ return ValueSupport.toValue(_zeroFmt.format(bd));
+ }
+ }
+
+ private static abstract class BDFormat
+ {
+ public int getMaxDecimalDigits() {
+ return Integer.MAX_VALUE;
+ }
+
+ public abstract String format(BigDecimal bd);
+ }
+
+ private static final class LiteralBDFormat extends BDFormat
+ {
+ private final String _str;
+
+ private LiteralBDFormat(String str) {
+ _str = str;
+ }
+
+ @Override
+ public String format(BigDecimal bd) {
+ return _str;
+ }
+ }
+
+ private static class BaseBDFormat extends BDFormat
+ {
+ private final NumberFormat _nf;
+
+ private BaseBDFormat(NumberFormat nf) {
+ _nf = nf;
+ }
+
+ @Override
+ public String format(BigDecimal bd) {
+ return _nf.format(bd);
+ }
+ }
+
+ private static final class DecimalBDFormat extends BaseBDFormat
+ {
+ private final int _maxDecDigits;
+
+ private DecimalBDFormat(DecimalFormat df) {
+ super(df);
+
+ int maxDecDigits = df.getMaximumFractionDigits();
+ int mult = df.getMultiplier();
+ while(mult > 1) {
+ ++maxDecDigits;
+ mult /= 10;
+ }
+ _maxDecDigits = maxDecDigits;
+ }
+
+ @Override
+ public int getMaxDecimalDigits() {
+ return _maxDecDigits;
}
}
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java
index aa978e2..b9cbb1a 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java
@@ -47,10 +47,12 @@ public class FunctionSupport
_maxParams = maxParams;
}
+ @Override
public String getName() {
return _name;
}
+ @Override
public boolean isPure() {
// most functions are probably pure, so make this the default
return true;
@@ -94,6 +96,7 @@ public class FunctionSupport
return false;
}
+ @Override
public final Value eval(EvalContext ctx, Value... params) {
try {
validateNumParams(params);
@@ -112,6 +115,7 @@ public class FunctionSupport
super(name, 1, 1);
}
+ @Override
public final Value eval(EvalContext ctx, Value... params) {
try {
validateNumParams(params);
@@ -130,6 +134,7 @@ public class FunctionSupport
super(name, 1, 1);
}
+ @Override
public final Value eval(EvalContext ctx, Value... params) {
try {
validateNumParams(params);
@@ -152,6 +157,7 @@ public class FunctionSupport
super(name, 2, 2);
}
+ @Override
public final Value eval(EvalContext ctx, Value... params) {
try {
validateNumParams(params);
@@ -170,6 +176,7 @@ public class FunctionSupport
super(name, 3, 3);
}
+ @Override
public final Value eval(EvalContext ctx, Value... params) {
try {
validateNumParams(params);
@@ -193,6 +200,7 @@ public class FunctionSupport
super(name, minParams, maxParams);
}
+ @Override
public final Value eval(EvalContext ctx, Value... params) {
try {
validateNumParams(params);
@@ -215,14 +223,17 @@ public class FunctionSupport
_name = _delegate.getName() + NON_VAR_SUFFIX;
}
+ @Override
public String getName() {
return _name;
}
+ @Override
public boolean isPure() {
return _delegate.isPure();
}
+ @Override
public Value eval(EvalContext ctx, Value... params) {
Value result = _delegate.eval(ctx, params);
if(result.isNull()) {
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java
index 30025ad..27aa715 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java
@@ -33,10 +33,12 @@ public class LongValue extends BaseNumericValue
_val = val;
}
+ @Override
public Type getType() {
return Type.LONG;
}
+ @Override
public Object get() {
return _val;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/NumberFormatter.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/NumberFormatter.java
index ce251c2..57a5e35 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/NumberFormatter.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/NumberFormatter.java
@@ -222,5 +222,15 @@ public class NumberFormatter
FieldPosition pos) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public int getMaximumFractionDigits() {
+ return _df.getMaximumFractionDigits();
+ }
+
+ @Override
+ public int getMinimumFractionDigits() {
+ return _df.getMinimumFractionDigits();
+ }
}
}
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..ff799be 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;
/**
*
@@ -41,10 +40,12 @@ public class StringValue extends BaseValue
_val = val;
}
+ @Override
public Type getType() {
return Type.STRING;
}
+ @Override
public Object get() {
return _val;
}
@@ -84,7 +85,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..e7be9bc 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;
@@ -38,9 +39,11 @@ public class ValueSupport
@Override public boolean isNull() {
return true;
}
+ @Override
public Type getType() {
return Type.NULL;
}
+ @Override
public Object get() {
return null;
}
@@ -99,36 +102,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 +152,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/AppendQueryImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/AppendQueryImpl.java
index 140fde3..3567c57 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/query/AppendQueryImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/AppendQueryImpl.java
@@ -36,10 +36,12 @@ public class AppendQueryImpl extends BaseSelectQueryImpl implements AppendQuery
super(name, rows, objectId, objectFlag, Type.APPEND);
}
+ @Override
public String getTargetTable() {
return getTypeRow().name1;
}
+ @Override
public List<String> getTargetColumns() {
return new RowFormatter(getTargetRows()) {
@Override protected void format(StringBuilder builder, Row row) {
@@ -48,14 +50,17 @@ public class AppendQueryImpl extends BaseSelectQueryImpl implements AppendQuery
}.format();
}
+ @Override
public String getRemoteDbPath() {
return getTypeRow().name2;
}
+ @Override
public String getRemoteDbType() {
return getTypeRow().expression;
}
+ @Override
public List<String> getValues() {
return new RowFormatter(getValueRows()) {
@Override protected void format(StringBuilder builder, Row row) {
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/query/BaseSelectQueryImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/BaseSelectQueryImpl.java
index c213dd0..691ca9a 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/query/BaseSelectQueryImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/BaseSelectQueryImpl.java
@@ -77,6 +77,7 @@ public abstract class BaseSelectQueryImpl extends QueryImpl
}
}
+ @Override
public String getSelectType()
{
if(hasFlag(DISTINCT_SELECT_TYPE)) {
@@ -99,6 +100,7 @@ public abstract class BaseSelectQueryImpl extends QueryImpl
return DEFAULT_TYPE;
}
+ @Override
public List<String> getSelectColumns()
{
List<String> result = (new RowFormatter(getColumnRows()) {
@@ -143,6 +145,7 @@ public abstract class BaseSelectQueryImpl extends QueryImpl
return super.getWhereExpression();
}
+ @Override
public List<String> getGroupings()
{
return (new RowFormatter(getGroupByRows()) {
@@ -152,6 +155,7 @@ public abstract class BaseSelectQueryImpl extends QueryImpl
}).format();
}
+ @Override
public String getHavingExpression()
{
return getHavingRow().expression;
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/query/CrossTabQueryImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/CrossTabQueryImpl.java
index 930f785..240e085 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/query/CrossTabQueryImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/CrossTabQueryImpl.java
@@ -59,6 +59,7 @@ public class CrossTabQueryImpl extends BaseSelectQueryImpl
CROSSTAB_PIVOT_FLAG));
}
+ @Override
public String getTransformExpression() {
Row row = getTransformRow();
if(row.expression == null) {
@@ -69,6 +70,7 @@ public class CrossTabQueryImpl extends BaseSelectQueryImpl
return toAlias(builder, row.name1).toString();
}
+ @Override
public String getPivotExpression() {
return getPivotRow().expression;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/query/DataDefinitionQueryImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/DataDefinitionQueryImpl.java
index 5d1024f..5496e29 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/query/DataDefinitionQueryImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/DataDefinitionQueryImpl.java
@@ -34,6 +34,7 @@ public class DataDefinitionQueryImpl extends QueryImpl
super(name, rows, objectId, objectFlag, Type.DATA_DEFINITION);
}
+ @Override
public String getDDLString() {
return getTypeRow().expression;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/query/MakeTableQueryImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/MakeTableQueryImpl.java
index 7a153a5..27880cf 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/query/MakeTableQueryImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/MakeTableQueryImpl.java
@@ -35,14 +35,17 @@ public class MakeTableQueryImpl extends BaseSelectQueryImpl
super(name, rows, objectId, objectFlag, Type.MAKE_TABLE);
}
+ @Override
public String getTargetTable() {
return getTypeRow().name1;
}
+ @Override
public String getRemoteDbPath() {
return getTypeRow().name2;
}
+ @Override
public String getRemoteDbType() {
return getTypeRow().expression;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/query/PassthroughQueryImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/PassthroughQueryImpl.java
index cfbcdc5..56b672b 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/query/PassthroughQueryImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/PassthroughQueryImpl.java
@@ -34,10 +34,12 @@ public class PassthroughQueryImpl extends QueryImpl implements PassthroughQuery
super(name, rows, objectId, objectFlag, Type.PASSTHROUGH);
}
+ @Override
public String getConnectionString() {
return getTypeRow().name1;
}
+ @Override
public String getPassthroughString() {
return getTypeRow().expression;
}
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..2bbc279 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;
@@ -73,6 +73,7 @@ public abstract class QueryImpl implements Query
/**
* Returns the name of the query.
*/
+ @Override
public String getName() {
return _name;
}
@@ -80,10 +81,12 @@ public abstract class QueryImpl implements Query
/**
* Returns the type of the query.
*/
+ @Override
public Type getType() {
return _type;
}
+ @Override
public boolean isHidden() {
return((_objectFlag & DatabaseImpl.HIDDEN_OBJECT_FLAG) != 0);
}
@@ -91,10 +94,12 @@ public abstract class QueryImpl implements Query
/**
* Returns the unique object id of the query.
*/
+ @Override
public int getObjectId() {
return _objectId;
}
+ @Override
public int getObjectFlag() {
return _objectFlag;
}
@@ -170,6 +175,7 @@ public abstract class QueryImpl implements Query
}
}
+ @Override
public List<String> getParameters()
{
return (new RowFormatter(getParameterRows()) {
@@ -299,6 +305,7 @@ public abstract class QueryImpl implements Query
}).format();
}
+ @Override
public String getOwnerAccessType() {
return(hasFlag(OWNER_ACCESS_SELECT_TYPE) ?
"WITH OWNERACCESS OPTION" : DEFAULT_TYPE);
@@ -316,6 +323,7 @@ public abstract class QueryImpl implements Query
/**
* Returns the actual SQL string which this query data represents.
*/
+ @Override
public String toSQLString()
{
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/query/UnionQueryImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/UnionQueryImpl.java
index f74aefd..c9b198b 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/query/UnionQueryImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/UnionQueryImpl.java
@@ -35,14 +35,17 @@ public class UnionQueryImpl extends QueryImpl implements UnionQuery
super(name, rows, objectId, objectFlag, Type.UNION);
}
+ @Override
public String getUnionType() {
return(hasFlag(UNION_FLAG) ? DEFAULT_TYPE : "ALL");
}
+ @Override
public String getUnionString1() {
return getUnionString(UNION_PART1);
}
+ @Override
public String getUnionString2() {
return getUnionString(UNION_PART2);
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/query/UpdateQueryImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/query/UpdateQueryImpl.java
index 9cf722a..afea512 100644
--- a/src/main/java/com/healthmarketscience/jackcess/impl/query/UpdateQueryImpl.java
+++ b/src/main/java/com/healthmarketscience/jackcess/impl/query/UpdateQueryImpl.java
@@ -36,21 +36,25 @@ public class UpdateQueryImpl extends QueryImpl implements UpdateQuery
super(name, rows, objectId, objectFlag, Type.UPDATE);
}
+ @Override
public List<String> getTargetTables()
{
return super.getFromTables();
}
+ @Override
public String getRemoteDbPath()
{
return super.getFromRemoteDbPath();
}
+ @Override
public String getRemoteDbType()
{
return super.getFromRemoteDbType();
}
+ @Override
public List<String> getNewValues()
{
return (new RowFormatter(getColumnRows()) {
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/CaseInsensitiveColumnMatcher.java b/src/main/java/com/healthmarketscience/jackcess/util/CaseInsensitiveColumnMatcher.java
index e44002e..f527bb2 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/CaseInsensitiveColumnMatcher.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/CaseInsensitiveColumnMatcher.java
@@ -41,6 +41,7 @@ public class CaseInsensitiveColumnMatcher implements ColumnMatcher {
public CaseInsensitiveColumnMatcher() {
}
+ @Override
public boolean matches(Table table, String columnName, Object value1,
Object value2)
{
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java b/src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java
index fce5289..e36c569 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;
}
@@ -132,6 +133,7 @@ public abstract class CustomLinkResolver implements LinkResolver
* @see #createTempDb
* @see LinkResolver#DEFAULT
*/
+ @Override
public Database resolveLinkedDatabase(Database linkerDb, String linkeeFileName)
throws IOException
{
@@ -161,24 +163,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 +200,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 +263,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 +290,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/EntryIterableBuilder.java b/src/main/java/com/healthmarketscience/jackcess/util/EntryIterableBuilder.java
index 9a112d8..220d525 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/EntryIterableBuilder.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/EntryIterableBuilder.java
@@ -106,6 +106,7 @@ public class EntryIterableBuilder implements Iterable<Row>
return this;
}
+ @Override
public Iterator<Row> iterator() {
return ((IndexCursorImpl)_cursor).entryIterator(this);
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/ErrorHandler.java b/src/main/java/com/healthmarketscience/jackcess/util/ErrorHandler.java
index f47ea54..fe9197b 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/ErrorHandler.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/ErrorHandler.java
@@ -38,6 +38,7 @@ public interface ErrorHandler
* @usage _general_field_
*/
public static final ErrorHandler DEFAULT = new ErrorHandler() {
+ @Override
public Object handleRowError(Column column, byte[] columnData,
Location location, Exception error)
throws IOException
@@ -84,6 +85,7 @@ public interface ErrorHandler
/**
* Contains details about the errored row, useful for debugging.
*/
+ @Override
public String toString();
}
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/IterableBuilder.java b/src/main/java/com/healthmarketscience/jackcess/util/IterableBuilder.java
index edb635d..3865152 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/IterableBuilder.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/IterableBuilder.java
@@ -178,6 +178,7 @@ public class IterableBuilder implements Iterable<Row>
return this;
}
+ @Override
public Iterator<Row> iterator() {
return ((CursorImpl)_cursor).iterator(this);
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/LinkResolver.java b/src/main/java/com/healthmarketscience/jackcess/util/LinkResolver.java
index 5310449..c1beee3 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/LinkResolver.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/LinkResolver.java
@@ -36,6 +36,7 @@ public interface LinkResolver
* @usage _general_field_
*/
public static final LinkResolver DEFAULT = new LinkResolver() {
+ @Override
public Database resolveLinkedDatabase(Database linkerDb,
String linkeeFileName)
throws IOException
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java b/src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java
index 528ddbb..ba9a037 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/>
+ * <p>
* 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/>
+ * <p>
* 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..a84f122 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/>
+ * <p>
* 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/>
+ * <p>
* 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/>
+ * <p>
* 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/>
+ * <p>
* <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/>
+ * <p>
* <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/>
+ * <p>
* <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..023a917 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/>
+ * <p>
* 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/ReplacementErrorHandler.java b/src/main/java/com/healthmarketscience/jackcess/util/ReplacementErrorHandler.java
index d7e2e2b..3298fd3 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/ReplacementErrorHandler.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/ReplacementErrorHandler.java
@@ -47,6 +47,7 @@ public class ReplacementErrorHandler implements ErrorHandler
_replacement = replacement;
}
+ @Override
public Object handleRowError(Column column, byte[] columnData,
Location location, Exception error)
throws IOException
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/RowFilter.java b/src/main/java/com/healthmarketscience/jackcess/util/RowFilter.java
index 440d9dd..a579176 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,12 +163,14 @@ public abstract class RowFilter
* iterable, returning only rows for which the {@link RowFilter#matches}
* method returns {@code true}
*/
- public Iterator<Row> iterator()
+ @Override
+ public Iterator<Row> iterator()
{
return new Iterator<Row>() {
private final Iterator<? extends Row> _iter = _iterable.iterator();
private Row _next;
+ @Override
public boolean hasNext() {
while(_iter.hasNext()) {
_next = _iter.next();
@@ -180,6 +182,7 @@ public abstract class RowFilter
return false;
}
+ @Override
public Row next() {
if(_next == null) {
throw new NoSuchElementException();
@@ -187,6 +190,7 @@ public abstract class RowFilter
return _next;
}
+ @Override
public void remove() {
throw new UnsupportedOperationException();
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java
index a564834..6661948 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.
@@ -41,6 +41,7 @@ public class SimpleColumnMatcher implements ColumnMatcher {
public SimpleColumnMatcher() {
}
+ @Override
public boolean matches(Table table, String columnName, Object value1,
Object value2)
{
@@ -48,7 +49,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 +59,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 +74,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/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java
index 3c77cda..62fe900 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java
@@ -31,6 +31,7 @@ public class SimpleColumnValidator implements ColumnValidator
{
public static final SimpleColumnValidator INSTANCE = new SimpleColumnValidator();
+ @Override
public Object validate(Column col, Object val) throws IOException {
return val;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java
index f36556b..0c9e6b6 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java
@@ -30,6 +30,7 @@ public class SimpleColumnValidatorFactory implements ColumnValidatorFactory
public static final SimpleColumnValidatorFactory INSTANCE =
new SimpleColumnValidatorFactory();
+ @Override
public ColumnValidator createValidator(Column col) {
return SimpleColumnValidator.INSTANCE;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/SimpleExportFilter.java b/src/main/java/com/healthmarketscience/jackcess/util/SimpleExportFilter.java
index f86a236..15a55b7 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/SimpleExportFilter.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/SimpleExportFilter.java
@@ -34,10 +34,12 @@ public class SimpleExportFilter implements ExportFilter {
public SimpleExportFilter() {
}
+ @Override
public List<Column> filterColumns(List<Column> columns) throws IOException {
return columns;
}
+ @Override
public Object[] filterRow(Object[] row) throws IOException {
return row;
}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/SimpleImportFilter.java b/src/main/java/com/healthmarketscience/jackcess/util/SimpleImportFilter.java
index f8fbab1..2d6b726 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/SimpleImportFilter.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/SimpleImportFilter.java
@@ -36,6 +36,7 @@ public class SimpleImportFilter implements ImportFilter {
public SimpleImportFilter() {
}
+ @Override
public List<ColumnBuilder> filterColumns(List<ColumnBuilder> destColumns,
ResultSetMetaData srcColumns)
throws SQLException, IOException
@@ -43,6 +44,7 @@ public class SimpleImportFilter implements ImportFilter {
return destColumns;
}
+ @Override
public Object[] filterRow(Object[] row)
throws SQLException, IOException
{
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/TableIterableBuilder.java b/src/main/java/com/healthmarketscience/jackcess/util/TableIterableBuilder.java
index 168d758..da6d1b5 100644
--- a/src/main/java/com/healthmarketscience/jackcess/util/TableIterableBuilder.java
+++ b/src/main/java/com/healthmarketscience/jackcess/util/TableIterableBuilder.java
@@ -87,6 +87,7 @@ public class TableIterableBuilder implements Iterable<Table>
return setIncludeLinkedTables(false);
}
+ @Override
public Iterator<Table> iterator() {
return ((DatabaseImpl)_db).iterator(this);
}