git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1280 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-3.0.0
@@ -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><span class="UsageGeneral"><span class="UsageGeneralHeader">General</span>: This $1 is general use.</span></value> | |||
</replacement> | |||
<replacement> | |||
<token>_intermediate_(method|class|field)_</token> | |||
<value><span class="UsageIntermediate"><span class="UsageIntermediateHeader">Intermediate</span>: This $1 requires moderate API knowledge.</span></value> | |||
</replacement> | |||
<replacement> | |||
<token>_advanced_(method|class|field)_</token> | |||
<value><span class="UsageAdvanced"><span class="UsageAdvancedHeader">Advanced</span>: This $1 is for advanced/internal use.</span></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> |
@@ -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. |
@@ -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 -> 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 -> 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 -> 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 -> 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 -> Column value). | |||
*/ | |||
public Row getCurrentRow() throws IOException; | |||
/** | |||
* Returns the current row in this cursor (Column name -> Column value). | |||
* Returns the current row in this cursor (Column name -> Column value). | |||
* @param columnNames Only column names in this collection will be returned | |||
*/ | |||
public Row getCurrentRow(Collection<String> columnNames) |
@@ -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")); |
@@ -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), | |||
/** |
@@ -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); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -41,9 +41,9 @@ public interface IndexCursor extends Cursor | |||
* @param entryValues the column values for the index's columns. | |||
* @return the matching row or {@code null} if a match could not be found. | |||
*/ | |||
public Row findRowByEntry(Object... entryValues) | |||
public Row findRowByEntry(Object... entryValues) | |||
throws IOException; | |||
/** | |||
* Moves to the first row (as defined by the cursor) where the index entries | |||
* match the given values. If a match is not found (or an exception is | |||
@@ -56,32 +56,32 @@ public interface IndexCursor extends Cursor | |||
* @return {@code true} if a valid row was found with the given values, | |||
* {@code false} if no row was found | |||
*/ | |||
public boolean findFirstRowByEntry(Object... entryValues) | |||
public boolean findFirstRowByEntry(Object... entryValues) | |||
throws IOException; | |||
/** | |||
* Moves to the first row (as defined by the cursor) where the index entries | |||
* are >= the given values. If a an exception is thrown, the cursor is | |||
* are >= the given values. If a an exception is thrown, the cursor is | |||
* restored to its previous state. | |||
* | |||
* @param entryValues the column values for the index's columns. | |||
*/ | |||
public void findClosestRowByEntry(Object... entryValues) | |||
public void findClosestRowByEntry(Object... entryValues) | |||
throws IOException; | |||
/** | |||
* Returns {@code true} if the current row matches the given index entries. | |||
* | |||
* | |||
* @param entryValues the column values for the index's columns. | |||
*/ | |||
public boolean currentRowMatchesEntry(Object... entryValues) | |||
public boolean currentRowMatchesEntry(Object... entryValues) | |||
throws IOException; | |||
/** | |||
* Convenience method for constructing a new EntryIterableBuilder for this | |||
* cursor. An EntryIterableBuilder provides a variety of options for more | |||
* flexible iteration based on a specific index entry. | |||
* | |||
* | |||
* @param entryValues the column values for the index's columns. | |||
*/ | |||
public EntryIterableBuilder newEntryIterable(Object... entryValues); |
@@ -29,4 +29,8 @@ public class InvalidValueException extends JackcessException | |||
public InvalidValueException(String msg) { | |||
super(msg); | |||
} | |||
public InvalidValueException(String msg, Throwable cause) { | |||
super(msg, cause); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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") |
@@ -20,13 +20,14 @@ import java.io.IOException; | |||
import java.util.Date; | |||
import java.util.Map; | |||
import java.math.BigDecimal; | |||
import java.time.LocalDateTime; | |||
import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey; | |||
import com.healthmarketscience.jackcess.util.OleBlob; | |||
/** | |||
* A row of data as column name->value pairs. Values are strongly typed, and | |||
* A row of data as column name->value pairs. Values are strongly typed, and | |||
* column names are case sensitive. | |||
* | |||
* @author James Ahlborn | |||
@@ -35,7 +36,7 @@ import com.healthmarketscience.jackcess.util.OleBlob; | |||
public interface Row extends Map<String,Object> | |||
{ | |||
/** | |||
* @return the id of this row | |||
* @return the id of this row | |||
*/ | |||
public RowId getId(); | |||
@@ -90,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; |
@@ -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 -> columnValue to an array of row values | |||
* appropriate for a call to {@link #addRow(Object...)}. | |||
* @usage _general_method_ | |||
*/ | |||
public Object[] asRow(Map<String,?> rowMap); | |||
/** | |||
* Converts a map of columnName -> columnValue to an array of row values | |||
* Converts a map of columnName -> columnValue to an array of row values | |||
* appropriate for a call to {@link Cursor#updateCurrentRow(Object...)}. | |||
* @usage _general_method_ | |||
*/ | |||
@@ -203,7 +203,7 @@ public interface Table extends Iterable<Row> | |||
/** | |||
* Calls {@link #asRow} on the given row map and passes the result to {@link | |||
* #addRow}. | |||
* <p/> | |||
* <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 -> Column value) (uses | |||
* the {@link #getDefaultCursor default cursor}) | |||
* @usage _general_method_ | |||
*/ |
@@ -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") |
@@ -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); | |||
} |
@@ -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; | |||
@@ -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(); | |||
} |
@@ -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 |
@@ -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 |
@@ -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); | |||
} |
@@ -30,19 +30,18 @@ import java.util.Locale; | |||
*/ | |||
public class TemporalConfig | |||
{ | |||
public static final String US_DATE_FORMAT = "M/d/yyyy"; | |||
public static final String US_DATE_IMPLICIT_YEAR_FORMAT = "M/d"; | |||
public static final String US_DATE_FORMAT = "M/d[/uuuu]"; | |||
public static final String US_TIME_FORMAT_12_FORMAT = "h:mm:ss a"; | |||
public static final String US_TIME_FORMAT_24_FORMAT = "H:mm:ss"; | |||
public static final String US_LONG_DATE_FORMAT = "EEEE, MMMM dd, yyyy"; | |||
public static final String US_LONG_DATE_FORMAT = "EEEE, MMMM dd, uuuu"; | |||
public static final String MEDIUM_DATE_FORMAT = "dd-MMM-yy"; | |||
public static final String MEDIUM_DATE_FORMAT = "dd-MMM-uu"; | |||
public static final String MEDIUM_TIME_FORMAT = "hh:mm a"; | |||
public static final String SHORT_TIME_FORMAT = "HH:mm"; | |||
/** default implementation which is configured for the US locale */ | |||
public static final TemporalConfig US_TEMPORAL_CONFIG = new TemporalConfig( | |||
US_DATE_FORMAT, US_DATE_IMPLICIT_YEAR_FORMAT, US_LONG_DATE_FORMAT, | |||
US_DATE_FORMAT, US_LONG_DATE_FORMAT, | |||
US_TIME_FORMAT_12_FORMAT, US_TIME_FORMAT_24_FORMAT, '/', ':', Locale.US); | |||
public enum Type { | |||
@@ -133,8 +132,8 @@ public class TemporalConfig | |||
} | |||
} | |||
private final Locale _locale; | |||
private final String _dateFormat; | |||
private final String _dateImplicitYearFormat; | |||
private final String _longDateFormat; | |||
private final String _timeFormat12; | |||
private final String _timeFormat24; | |||
@@ -142,16 +141,15 @@ public class TemporalConfig | |||
private final char _timeSeparator; | |||
private final String _dateTimeFormat12; | |||
private final String _dateTimeFormat24; | |||
private final DateFormatSymbols _symbols; | |||
private final String[] _amPmStrings; | |||
/** | |||
* Instantiates a new TemporalConfig with the given configuration. Note | |||
* that the date/time format variants will be created by concatenating the | |||
* relevant date and time formats, separated by a single space, e.g. "<date> | |||
* <time>". | |||
* relevant date and time formats, separated by a single space, | |||
* e.g. "<date> <time>". | |||
* | |||
* @param dateFormat the date (no time) format | |||
* @param dateImplicitYearFormat the date (no time) with no year format | |||
* @param timeFormat12 the 12 hour time format | |||
* @param timeFormat24 the 24 hour time format | |||
* @param dateSeparator the primary separator used to separate elements in | |||
@@ -163,21 +161,26 @@ public class TemporalConfig | |||
* string. This value should differ from the | |||
* dateSeparator. | |||
*/ | |||
public TemporalConfig(String dateFormat, String dateImplicitYearFormat, | |||
String longDateFormat, | |||
public TemporalConfig(String dateFormat, String longDateFormat, | |||
String timeFormat12, String timeFormat24, | |||
char dateSeparator, char timeSeparator, Locale locale) | |||
{ | |||
_locale = locale; | |||
_dateFormat = dateFormat; | |||
_dateImplicitYearFormat = dateImplicitYearFormat; | |||
_longDateFormat = longDateFormat; | |||
_timeFormat12 = timeFormat12; | |||
_timeFormat24 = timeFormat24; | |||
_dateSeparator = dateSeparator; | |||
_timeSeparator = timeSeparator; | |||
_dateTimeFormat12 = _dateFormat + " " + _timeFormat12; | |||
_dateTimeFormat24 = _dateFormat + " " + _timeFormat24; | |||
_symbols = DateFormatSymbols.getInstance(locale); | |||
_dateTimeFormat12 = toDateTimeFormat(_dateFormat, _timeFormat12); | |||
_dateTimeFormat24 = toDateTimeFormat(_dateFormat, _timeFormat24); | |||
// there doesn't seem to be a good/easy way to get this in new jave.time | |||
// api, so just use old api | |||
_amPmStrings = DateFormatSymbols.getInstance(locale).getAmPmStrings(); | |||
} | |||
public Locale getLocale() { | |||
return _locale; | |||
} | |||
public String getDateFormat() { | |||
@@ -252,24 +255,8 @@ public class TemporalConfig | |||
} | |||
} | |||
public String getImplicitYearDateTimeFormat(Type type) { | |||
switch(type) { | |||
case DATE: | |||
return _dateImplicitYearFormat; | |||
case DATE_TIME: | |||
return toDateTimeFormat(_dateImplicitYearFormat, getDefaultTimeFormat()); | |||
case DATE_TIME_12: | |||
return toDateTimeFormat(_dateImplicitYearFormat, getTimeFormat12()); | |||
case DATE_TIME_24: | |||
return toDateTimeFormat(_dateImplicitYearFormat, getTimeFormat24()); | |||
default: | |||
throw new IllegalArgumentException( | |||
"the given format does not include a date " + type); | |||
} | |||
} | |||
public DateFormatSymbols getDateFormatSymbols() { | |||
return _symbols; | |||
public String[] getAmPmStrings() { | |||
return _amPmStrings; | |||
} | |||
private static String toDateTimeFormat(String dateFormat, String timeFormat) { |
@@ -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 |
@@ -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. | |||
* |
@@ -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); | |||
} |
@@ -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. | |||
* |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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 { |
@@ -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 -> 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; | |||
} |
@@ -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) { |
@@ -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; | |||
} | |||
} |
@@ -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(); |
@@ -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(); | |||
} |
@@ -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 |
@@ -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); | |||
} |
@@ -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; |
@@ -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. |
@@ -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); |
@@ -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; | |||
} |
@@ -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(); | |||
} |
@@ -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; | |||
} |
@@ -103,6 +103,7 @@ public class PropertyMaps implements Iterable<PropertyMapImpl> | |||
return map; | |||
} | |||
@Override | |||
public Iterator<PropertyMapImpl> iterator() { | |||
return _maps.values().iterator(); | |||
} |
@@ -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; |
@@ -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); | |||
} |
@@ -20,6 +20,7 @@ import java.io.IOException; | |||
import java.util.LinkedHashMap; | |||
import java.util.Date; | |||
import java.math.BigDecimal; | |||
import java.time.LocalDateTime; | |||
import com.healthmarketscience.jackcess.Row; | |||
import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey; | |||
@@ -27,8 +28,8 @@ import com.healthmarketscience.jackcess.util.OleBlob; | |||
/** | |||
* A row of data as column->value pairs. | |||
* </p> | |||
* A row of data as column->value pairs. | |||
* <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); |
@@ -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 -> 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 -> 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; | |||
} |
@@ -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; | |||
} | |||
@@ -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(); | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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(); |
@@ -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); | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} |
@@ -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: |
@@ -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; | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
}); | |||
@@ -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: |
@@ -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 |
@@ -33,10 +33,12 @@ public class DoubleValue extends BaseNumericValue | |||
_val = val; | |||
} | |||
@Override | |||
public Type getType() { | |||
return Type.DOUBLE; | |||
} | |||
@Override | |||
public Object get() { | |||
return _val; | |||
} |
@@ -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 + " "; | |||
} | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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()) { |
@@ -33,10 +33,12 @@ public class LongValue extends BaseNumericValue | |||
_val = val; | |||
} | |||
@Override | |||
public Type getType() { | |||
return Type.LONG; | |||
} | |||
@Override | |||
public Object get() { | |||
return _val; | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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 | |||
} |
@@ -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); | |||
} | |||
/** |
@@ -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) { |
@@ -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; |
@@ -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; | |||
} |
@@ -34,6 +34,7 @@ public class DataDefinitionQueryImpl extends QueryImpl | |||
super(name, rows, objectId, objectFlag, Type.DATA_DEFINITION); | |||
} | |||
@Override | |||
public String getDDLString() { | |||
return getTypeRow().expression; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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()) { |
@@ -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(); |
@@ -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); | |||
} |
@@ -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()) { |
@@ -41,6 +41,7 @@ public class CaseInsensitiveColumnMatcher implements ColumnMatcher { | |||
public CaseInsensitiveColumnMatcher() { | |||
} | |||
@Override | |||
public boolean matches(Table table, String columnName, Object value1, | |||
Object value2) | |||
{ |
@@ -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); | |||
} | |||
} |
@@ -106,6 +106,7 @@ public class EntryIterableBuilder implements Iterable<Row> | |||
return this; | |||
} | |||
@Override | |||
public Iterator<Row> iterator() { | |||
return ((IndexCursorImpl)_cursor).entryIterator(this); | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -178,6 +178,7 @@ public class IterableBuilder implements Iterable<Row> | |||
return this; | |||
} | |||
@Override | |||
public Iterator<Row> iterator() { | |||
return ((CursorImpl)_cursor).iterator(this); | |||
} |
@@ -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 |
@@ -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(); | |||
} | |||
} | |||
} | |||
} |
@@ -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; |
@@ -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. | |||
* |
@@ -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 |
@@ -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(); | |||
} |
@@ -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))); | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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 | |||
{ |