Browse Source

merge branch jdk8 changes through r1279

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1280 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-3.0.0
James Ahlborn 5 years ago
parent
commit
d231efd46c
100 changed files with 3623 additions and 1255 deletions
  1. 41
    44
      pom.xml
  2. 25
    0
      src/changes/changes.xml
  3. 10
    9
      src/main/java/com/healthmarketscience/jackcess/Cursor.java
  4. 2
    2
      src/main/java/com/healthmarketscience/jackcess/CursorBuilder.java
  5. 5
    3
      src/main/java/com/healthmarketscience/jackcess/DataType.java
  6. 53
    8
      src/main/java/com/healthmarketscience/jackcess/Database.java
  7. 36
    17
      src/main/java/com/healthmarketscience/jackcess/DatabaseBuilder.java
  8. 33
    0
      src/main/java/com/healthmarketscience/jackcess/DateTimeType.java
  9. 8
    8
      src/main/java/com/healthmarketscience/jackcess/IndexCursor.java
  10. 4
    0
      src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java
  11. 8
    4
      src/main/java/com/healthmarketscience/jackcess/PropertyMap.java
  12. 1
    1
      src/main/java/com/healthmarketscience/jackcess/RelationshipBuilder.java
  13. 18
    3
      src/main/java/com/healthmarketscience/jackcess/Row.java
  14. 8
    7
      src/main/java/com/healthmarketscience/jackcess/Table.java
  15. 1
    1
      src/main/java/com/healthmarketscience/jackcess/TableBuilder.java
  16. 22
    6
      src/main/java/com/healthmarketscience/jackcess/complex/Attachment.java
  17. 37
    9
      src/main/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java
  18. 10
    0
      src/main/java/com/healthmarketscience/jackcess/complex/Version.java
  19. 5
    4
      src/main/java/com/healthmarketscience/jackcess/expr/Identifier.java
  20. 7
    8
      src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java
  21. 7
    1
      src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java
  22. 21
    34
      src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java
  23. 3
    3
      src/main/java/com/healthmarketscience/jackcess/expr/Value.java
  24. 10
    10
      src/main/java/com/healthmarketscience/jackcess/expr/package-info.java
  25. 31
    8
      src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java
  26. 1
    1
      src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java
  27. 386
    76
      src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
  28. 20
    7
      src/main/java/com/healthmarketscience/jackcess/impl/CompoundOleUtil.java
  29. 45
    1
      src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java
  30. 9
    11
      src/main/java/com/healthmarketscience/jackcess/impl/CustomToStringStyle.java
  31. 20
    14
      src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java
  32. 159
    52
      src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java
  33. 34
    0
      src/main/java/com/healthmarketscience/jackcess/impl/DateTimeContext.java
  34. 9
    0
      src/main/java/com/healthmarketscience/jackcess/impl/DefaultCodecProvider.java
  35. 6
    0
      src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java
  36. 181
    174
      src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java
  37. 13
    1
      src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java
  38. 1
    1
      src/main/java/com/healthmarketscience/jackcess/impl/IndexPageCache.java
  39. 1
    0
      src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java
  40. 34
    1
      src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java
  41. 3
    0
      src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java
  42. 20
    1
      src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java
  43. 1
    0
      src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java
  44. 13
    0
      src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java
  45. 2
    1
      src/main/java/com/healthmarketscience/jackcess/impl/RowIdImpl.java
  46. 23
    3
      src/main/java/com/healthmarketscience/jackcess/impl/RowImpl.java
  47. 37
    3
      src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java
  48. 43
    10
      src/main/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java
  49. 21
    0
      src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexColumnInfoImpl.java
  50. 90
    33
      src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java
  51. 4
    0
      src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnInfoImpl.java
  52. 16
    0
      src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnPropertyMap.java
  53. 5
    0
      src/main/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java
  54. 50
    25
      src/main/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java
  55. 13
    3
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java
  56. 1
    1
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java
  57. 12
    4
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java
  58. 2
    0
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java
  59. 3
    3
      src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java
  60. 8
    6
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java
  61. 187
    262
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java
  62. 7
    9
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java
  63. 1
    1
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java
  64. 3
    3
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java
  65. 2
    0
      src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java
  66. 47
    162
      src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java
  67. 24
    9
      src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java
  68. 1422
    83
      src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java
  69. 11
    0
      src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java
  70. 2
    0
      src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java
  71. 10
    0
      src/main/java/com/healthmarketscience/jackcess/impl/expr/NumberFormatter.java
  72. 4
    3
      src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java
  73. 30
    21
      src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java
  74. 5
    0
      src/main/java/com/healthmarketscience/jackcess/impl/query/AppendQueryImpl.java
  75. 4
    0
      src/main/java/com/healthmarketscience/jackcess/impl/query/BaseSelectQueryImpl.java
  76. 2
    0
      src/main/java/com/healthmarketscience/jackcess/impl/query/CrossTabQueryImpl.java
  77. 1
    0
      src/main/java/com/healthmarketscience/jackcess/impl/query/DataDefinitionQueryImpl.java
  78. 3
    0
      src/main/java/com/healthmarketscience/jackcess/impl/query/MakeTableQueryImpl.java
  79. 2
    0
      src/main/java/com/healthmarketscience/jackcess/impl/query/PassthroughQueryImpl.java
  80. 7
    8
      src/main/java/com/healthmarketscience/jackcess/impl/query/QueryFormat.java
  81. 9
    1
      src/main/java/com/healthmarketscience/jackcess/impl/query/QueryImpl.java
  82. 3
    0
      src/main/java/com/healthmarketscience/jackcess/impl/query/UnionQueryImpl.java
  83. 4
    0
      src/main/java/com/healthmarketscience/jackcess/impl/query/UpdateQueryImpl.java
  84. 1
    0
      src/main/java/com/healthmarketscience/jackcess/util/CaseInsensitiveColumnMatcher.java
  85. 26
    19
      src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java
  86. 1
    0
      src/main/java/com/healthmarketscience/jackcess/util/EntryIterableBuilder.java
  87. 2
    0
      src/main/java/com/healthmarketscience/jackcess/util/ErrorHandler.java
  88. 1
    0
      src/main/java/com/healthmarketscience/jackcess/util/IterableBuilder.java
  89. 1
    0
      src/main/java/com/healthmarketscience/jackcess/util/LinkResolver.java
  90. 70
    25
      src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java
  91. 6
    6
      src/main/java/com/healthmarketscience/jackcess/util/OleBlob.java
  92. 1
    1
      src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java
  93. 1
    0
      src/main/java/com/healthmarketscience/jackcess/util/ReplacementErrorHandler.java
  94. 20
    16
      src/main/java/com/healthmarketscience/jackcess/util/RowFilter.java
  95. 5
    4
      src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java
  96. 1
    0
      src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java
  97. 1
    0
      src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java
  98. 2
    0
      src/main/java/com/healthmarketscience/jackcess/util/SimpleExportFilter.java
  99. 2
    0
      src/main/java/com/healthmarketscience/jackcess/util/SimpleImportFilter.java
  100. 0
    0
      src/main/java/com/healthmarketscience/jackcess/util/TableIterableBuilder.java

+ 41
- 44
pom.xml View File

@@ -3,13 +3,13 @@
<parent>
<groupId>com.healthmarketscience</groupId>
<artifactId>openhms-parent</artifactId>
<version>1.1.10</version>
<version>2.0.0</version>
</parent>
<groupId>com.healthmarketscience.jackcess</groupId>
<artifactId>jackcess</artifactId>
<name>Jackcess</name>
<description>A pure Java library for reading from and writing to MS Access databases.</description>
<version>2.2.4-SNAPSHOT</version>
<version>3.0.0-SNAPSHOT</version>
<url>https://jackcess.sourceforge.io</url>
<inceptionYear>2005</inceptionYear>
<developers>
@@ -124,8 +124,8 @@
<threadCount>2</threadCount>
<systemProperties>
<property>
<name>log4j.configuration</name>
<value>log4j_test.properties</value>
<name>java.util.logging.config.file</name>
<value>logging_test.properties</value>
</property>
<property>
<name>com.healthmarketscience.jackcess.testFormats</name>
@@ -189,31 +189,53 @@
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<executions>
<execution>
<id>with-regex2</id>
<phase>site</phase>
<goals>
<goal>replace</goal>
</goals>
<configuration>
<includes>
<include>${project.build.directory}/site/apidocs/**/*.html</include>
</includes>
<regex>true</regex>
<replacements>
<replacement>
<token>_general_(method|class|field)_</token>
<value>&lt;span class="UsageGeneral"&gt;&lt;span class="UsageGeneralHeader"&gt;General&lt;/span&gt;: This $1 is general use.&lt;/span&gt;</value>
</replacement>
<replacement>
<token>_intermediate_(method|class|field)_</token>
<value>&lt;span class="UsageIntermediate"&gt;&lt;span class="UsageIntermediateHeader"&gt;Intermediate&lt;/span&gt;: This $1 requires moderate API knowledge.&lt;/span&gt;</value>
</replacement>
<replacement>
<token>_advanced_(method|class|field)_</token>
<value>&lt;span class="UsageAdvanced"&gt;&lt;span class="UsageAdvancedHeader"&gt;Advanced&lt;/span&gt;: This $1 is for advanced/internal use.&lt;/span&gt;</value>
</replacement>
</replacements>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.7</version>
<scope>runtime</scope>
<optional>true</optional>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<version>4.12</version>
<scope>test</scope>
</dependency>

@@ -221,7 +243,7 @@
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
<version>4.0.0</version>
<optional>true</optional>
</dependency>

@@ -250,14 +272,13 @@
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<additionalJOption>-J-DTaglets.ConfigurationFile=${basedir}/src/site/javadoc/taglets.properties -J-DTaglets.NoDefaultConfiguration=true</additionalJOption>
<minmemory>128m</minmemory>
<maxmemory>512</maxmemory>
<links>
<list>http://docs.oracle.com/javase/1.5.0/docs/api/</list>
<list>https://docs.oracle.com/javase/8/docs/api/</list>
<list>http://docs.oracle.com/javaee/5/api/</list>
</links>
<source>1.5</source>
<source>1.8</source>
<show>public</show>
<stylesheetfile>${basedir}/src/site/javadoc/stylesheet.css</stylesheetfile>
<tags>
@@ -267,16 +288,6 @@
<head>Usage:</head>
</tag>
</tags>
<taglets>
<taglet>
<tagletClass>net.sourceforge.taglets.Taglets</tagletClass>
<tagletArtifact>
<groupId>net.sourceforge.taglets</groupId>
<artifactId>taglets</artifactId>
<version>2.0.3</version>
</tagletArtifact>
</taglet>
</taglets>
<quiet>true</quiet>
</configuration>
</plugin>
@@ -289,20 +300,6 @@
</plugin>
</plugins>
</reporting>
<repositories>
<repository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>taglets</id>
<name>Taglets</name>
<url>http://maven.geotoolkit.org/</url>
<layout>default</layout>
</repository>
</repositories>
<distributionManagement>
<site>
<id>jackcess-build-site</id>

+ 25
- 0
src/changes/changes.xml View File

@@ -4,6 +4,31 @@
<author email="javajedi@users.sf.net">Tim McCune</author>
</properties>
<body>
<release version="3.0.0" date="TBD" description="Update to Java 8">
<action dev="jahlborn" type="update">
Jackcess now requires a Java 8+ runtime. As part of this update, all
dependencies have been updated to their latest versions.
</action>
<action dev="jahlborn" type="update">
Add support for Java 8 Temporal types. Date/time fields will now
accept as input most Temporal types (e.g. LocalDate, LocalTime,
LocalDateTime, etc). Additionally, date/time fields can optionally be
changed to output LocalDateTime instead of Date. This behavior is
configurable on a per-Database basis by setting the DateTimeType for
the database. All Date based APIs now have a parallel LocalDateTime
API. Note that only one of those will work depending on how
the Database is configured. The legacy Date support will be the
default initially, but is deprecated and may be removed in the future.
</action>
<action dev="jahlborn" type="update">
Add support for Path. Existing File based APIs now have parallel
versions which utilize Path.
</action>
<action dev="jahlborn" type="update">
The Apache commons-lang dependency has been replaced with
commons-lang3.
</action>
</release>
<release version="2.2.3" date="2019-02-05">
<action dev="jahlborn" type="fix">
Fix parsing of escaped double quotes in expressions.

+ 10
- 9
src/main/java/com/healthmarketscience/jackcess/Cursor.java View File

@@ -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)

+ 2
- 2
src/main/java/com/healthmarketscience/jackcess/CursorBuilder.java View File

@@ -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"));

+ 5
- 3
src/main/java/com/healthmarketscience/jackcess/DataType.java View File

@@ -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),
/**

+ 53
- 8
src/main/java/com/healthmarketscience/jackcess/Database.java View File

@@ -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_
@@ -178,6 +187,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,17 +394,33 @@ 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);
}

+ 36
- 17
src/main/java/com/healthmarketscience/jackcess/DatabaseBuilder.java View File

@@ -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);
}
}

+ 33
- 0
src/main/java/com/healthmarketscience/jackcess/DateTimeType.java View File

@@ -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;
}

+ 8
- 8
src/main/java/com/healthmarketscience/jackcess/IndexCursor.java View File

@@ -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);

+ 4
- 0
src/main/java/com/healthmarketscience/jackcess/InvalidValueException.java View File

@@ -29,4 +29,8 @@ public class InvalidValueException extends JackcessException
public InvalidValueException(String msg) {
super(msg);
}

public InvalidValueException(String msg, Throwable cause) {
super(msg, cause);
}
}

+ 8
- 4
src/main/java/com/healthmarketscience/jackcess/PropertyMap.java View File

@@ -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;
}

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/RelationshipBuilder.java View File

@@ -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")

+ 18
- 3
src/main/java/com/healthmarketscience/jackcess/Row.java View File

@@ -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,9 +91,23 @@ 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).
@@ -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;

+ 8
- 7
src/main/java/com/healthmarketscience/jackcess/Table.java View File

@@ -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_
*/

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/TableBuilder.java View File

@@ -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")

+ 22
- 6
src/main/java/com/healthmarketscience/jackcess/complex/Attachment.java View File

@@ -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);
}

+ 37
- 9
src/main/java/com/healthmarketscience/jackcess/complex/ComplexValueForeignKey.java View File

@@ -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;


+ 10
- 0
src/main/java/com/healthmarketscience/jackcess/complex/Version.java View File

@@ -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();
}

+ 5
- 4
src/main/java/com/healthmarketscience/jackcess/expr/Identifier.java View File

@@ -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

+ 7
- 8
src/main/java/com/healthmarketscience/jackcess/expr/LocaleContext.java View File

@@ -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

+ 7
- 1
src/main/java/com/healthmarketscience/jackcess/expr/NumericConfig.java View File

@@ -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);
}

+ 21
- 34
src/main/java/com/healthmarketscience/jackcess/expr/TemporalConfig.java View File

@@ -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) {

+ 3
- 3
src/main/java/com/healthmarketscience/jackcess/expr/Value.java View File

@@ -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

+ 10
- 10
src/main/java/com/healthmarketscience/jackcess/expr/package-info.java View File

@@ -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.
*

+ 31
- 8
src/main/java/com/healthmarketscience/jackcess/impl/BaseEvalContext.java View File

@@ -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);
}

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/CalculatedColumnUtil.java View File

@@ -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.
*

+ 386
- 76
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java View File

@@ -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());
}
@@ -1982,6 +2148,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
public static Object toInternalValue(DataType dataType, Object value,
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());
}
}
}

+ 20
- 7
src/main/java/com/healthmarketscience/jackcess/impl/CompoundOleUtil.java View File

@@ -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 {

+ 45
- 1
src/main/java/com/healthmarketscience/jackcess/impl/CursorImpl.java View File

@@ -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;
}

+ 9
- 11
src/main/java/com/healthmarketscience/jackcess/impl/CustomToStringStyle.java View File

@@ -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) {

+ 20
- 14
src/main/java/com/healthmarketscience/jackcess/impl/DBEvalContext.java View File

@@ -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;
}
}

+ 159
- 52
src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java View File

@@ -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);
}

/**
@@ -2025,6 +2099,17 @@ public class DatabaseImpl implements Database
return false;
}

/**
* 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();

+ 34
- 0
src/main/java/com/healthmarketscience/jackcess/impl/DateTimeContext.java View File

@@ -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();
}

+ 9
- 0
src/main/java/com/healthmarketscience/jackcess/impl/DefaultCodecProvider.java View File

@@ -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

+ 6
- 0
src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java View File

@@ -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);
}

+ 181
- 174
src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java
File diff suppressed because it is too large
View File


+ 13
- 1
src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java View File

@@ -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;

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/IndexPageCache.java View File

@@ -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.

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/impl/InternalColumnValidator.java View File

@@ -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);

+ 34
- 1
src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java View File

@@ -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;
}

+ 3
- 0
src/main/java/com/healthmarketscience/jackcess/impl/PageChannel.java View File

@@ -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();
}

+ 20
- 1
src/main/java/com/healthmarketscience/jackcess/impl/PropertyMapImpl.java View File

@@ -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;
}

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/impl/PropertyMaps.java View File

@@ -103,6 +103,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl>
return map;
}

@Override
public Iterator<PropertyMapImpl> iterator() {
return _maps.values().iterator();
}

+ 13
- 0
src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java View File

@@ -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;

+ 2
- 1
src/main/java/com/healthmarketscience/jackcess/impl/RowIdImpl.java View File

@@ -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);
}

+ 23
- 3
src/main/java/com/healthmarketscience/jackcess/impl/RowImpl.java View File

@@ -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);

+ 37
- 3
src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java View File

@@ -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;
}

+ 43
- 10
src/main/java/com/healthmarketscience/jackcess/impl/complex/AttachmentColumnInfoImpl.java View File

@@ -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;
}


+ 21
- 0
src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexColumnInfoImpl.java View File

@@ -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();
}

+ 90
- 33
src/main/java/com/healthmarketscience/jackcess/impl/complex/ComplexValueForeignKeyImpl.java View File

@@ -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,14 +185,31 @@ 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(
@@ -192,14 +222,33 @@ 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(
@@ -207,21 +256,21 @@ public class ComplexValueForeignKeyImpl extends ComplexValueForeignKey
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());
}
}

+ 4
- 0
src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnInfoImpl.java View File

@@ -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);
}

+ 16
- 0
src/main/java/com/healthmarketscience/jackcess/impl/complex/MultiValueColumnPropertyMap.java View File

@@ -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();

+ 5
- 0
src/main/java/com/healthmarketscience/jackcess/impl/complex/UnsupportedColumnInfoImpl.java View File

@@ -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);
}

+ 50
- 25
src/main/java/com/healthmarketscience/jackcess/impl/complex/VersionHistoryColumnInfoImpl.java View File

@@ -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();
}
}
}

+ 13
- 3
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseDelayedValue.java View File

@@ -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);
}

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseNumericValue.java View File

@@ -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);
}

+ 12
- 4
src/main/java/com/healthmarketscience/jackcess/impl/expr/BaseValue.java View File

@@ -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);
}

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/impl/expr/BigDecimalValue.java View File

@@ -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;
}

+ 3
- 3
src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java View File

@@ -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:

+ 8
- 6
src/main/java/com/healthmarketscience/jackcess/impl/expr/DateTimeValue.java View File

@@ -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;
}


+ 187
- 262
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultDateFunctions.java View File

@@ -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;
}
}

+ 7
- 9
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java View File

@@ -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));
}
});


+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultNumberFunctions.java View File

@@ -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:

+ 3
- 3
src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultTextFunctions.java View File

@@ -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

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/impl/expr/DoubleValue.java View File

@@ -33,10 +33,12 @@ public class DoubleValue extends BaseNumericValue
_val = val;
}

@Override
public Type getType() {
return Type.DOUBLE;
}

@Override
public Object get() {
return _val;
}

+ 47
- 162
src/main/java/com/healthmarketscience/jackcess/impl/expr/ExpressionTokenizer.java View File

@@ -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 + " ";
}
}

}

+ 24
- 9
src/main/java/com/healthmarketscience/jackcess/impl/expr/Expressionator.java View File

@@ -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);
}

+ 1422
- 83
src/main/java/com/healthmarketscience/jackcess/impl/expr/FormatUtil.java
File diff suppressed because it is too large
View File


+ 11
- 0
src/main/java/com/healthmarketscience/jackcess/impl/expr/FunctionSupport.java View File

@@ -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()) {

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/impl/expr/LongValue.java View File

@@ -33,10 +33,12 @@ public class LongValue extends BaseNumericValue
_val = val;
}

@Override
public Type getType() {
return Type.LONG;
}

@Override
public Object get() {
return _val;
}

+ 10
- 0
src/main/java/com/healthmarketscience/jackcess/impl/expr/NumberFormatter.java View File

@@ -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();
}
}
}

+ 4
- 3
src/main/java/com/healthmarketscience/jackcess/impl/expr/StringValue.java View File

@@ -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
}

+ 30
- 21
src/main/java/com/healthmarketscience/jackcess/impl/expr/ValueSupport.java View File

@@ -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);
}

/**

+ 5
- 0
src/main/java/com/healthmarketscience/jackcess/impl/query/AppendQueryImpl.java View File

@@ -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) {

+ 4
- 0
src/main/java/com/healthmarketscience/jackcess/impl/query/BaseSelectQueryImpl.java View File

@@ -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;

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/impl/query/CrossTabQueryImpl.java View File

@@ -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;
}

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/impl/query/DataDefinitionQueryImpl.java View File

@@ -34,6 +34,7 @@ public class DataDefinitionQueryImpl extends QueryImpl
super(name, rows, objectId, objectFlag, Type.DATA_DEFINITION);
}

@Override
public String getDDLString() {
return getTypeRow().expression;
}

+ 3
- 0
src/main/java/com/healthmarketscience/jackcess/impl/query/MakeTableQueryImpl.java View File

@@ -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;
}

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/impl/query/PassthroughQueryImpl.java View File

@@ -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;
}

+ 7
- 8
src/main/java/com/healthmarketscience/jackcess/impl/query/QueryFormat.java View File

@@ -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()) {

+ 9
- 1
src/main/java/com/healthmarketscience/jackcess/impl/query/QueryImpl.java View File

@@ -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();

+ 3
- 0
src/main/java/com/healthmarketscience/jackcess/impl/query/UnionQueryImpl.java View File

@@ -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);
}

+ 4
- 0
src/main/java/com/healthmarketscience/jackcess/impl/query/UpdateQueryImpl.java View File

@@ -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()) {

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/util/CaseInsensitiveColumnMatcher.java View File

@@ -41,6 +41,7 @@ public class CaseInsensitiveColumnMatcher implements ColumnMatcher {
public CaseInsensitiveColumnMatcher() {
}

@Override
public boolean matches(Table table, String columnName, Object value1,
Object value2)
{

+ 26
- 19
src/main/java/com/healthmarketscience/jackcess/util/CustomLinkResolver.java View File

@@ -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);
}
}

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/util/EntryIterableBuilder.java View File

@@ -106,6 +106,7 @@ public class EntryIterableBuilder implements Iterable<Row>
return this;
}

@Override
public Iterator<Row> iterator() {
return ((IndexCursorImpl)_cursor).entryIterator(this);
}

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/util/ErrorHandler.java View File

@@ -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();
}
}

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/util/IterableBuilder.java View File

@@ -178,6 +178,7 @@ public class IterableBuilder implements Iterable<Row>
return this;
}

@Override
public Iterator<Row> iterator() {
return ((CursorImpl)_cursor).iterator(this);
}

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/util/LinkResolver.java View File

@@ -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

+ 70
- 25
src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java View File

@@ -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,25 +113,62 @@ 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);
}
}

/**
* 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();
}
}
}
}

+ 6
- 6
src/main/java/com/healthmarketscience/jackcess/util/OleBlob.java View File

@@ -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;

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java View File

@@ -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.
*

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/util/ReplacementErrorHandler.java View File

@@ -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

+ 20
- 16
src/main/java/com/healthmarketscience/jackcess/util/RowFilter.java View File

@@ -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();
}

+ 5
- 4
src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnMatcher.java View File

@@ -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)));
}

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidator.java View File

@@ -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;
}

+ 1
- 0
src/main/java/com/healthmarketscience/jackcess/util/SimpleColumnValidatorFactory.java View File

@@ -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;
}

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/util/SimpleExportFilter.java View File

@@ -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;
}

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/util/SimpleImportFilter.java View File

@@ -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
{

+ 0
- 0
src/main/java/com/healthmarketscience/jackcess/util/TableIterableBuilder.java View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save