diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 0000000..fe25f0b --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,8 @@ +------------------------------------------------------------------------------ + qJava 2.0 [2014.04.02] +------------------------------------------------------------------------------ + + - Support for kdb+ protocol and types: v3.0, v2.6, v<=2.5 + - Synchronous and asynchronous queries + - Convenient asynchronous callbacks mechanism + - Logging of the raw protocol data in case of parsing failure. \ No newline at end of file diff --git a/LICENSE b/LICENSE index ad410e1..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ -Apache License + + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,7 +179,7 @@ Apache License APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +187,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,4 +199,4 @@ Apache License 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. \ No newline at end of file + limitations under the License. diff --git a/README.md b/README.md index 2875c49..f3fc84a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,12 @@ -qJava -===== + + +qJava 2.0 +========= + +The q/kdb+ interface is implemented as a set of Java classes and provides: +- Simple to use API +- Support for synchronous and asynchronous queries +- Convenient asynchronous callbacks mechanism +- Support for kdb+ protocol and types: v3.0, v2.6, v<=2.5 +- Uncompression of the IPC data stream +- Compatible with Java 5.0+ diff --git a/assembly.xml b/assembly.xml new file mode 100644 index 0000000..8df8e9a --- /dev/null +++ b/assembly.xml @@ -0,0 +1,34 @@ + + qJava + + zip + + + + target + + + *sources.jar + + + *.jar + + + + target/apidocs + apidocs + + **/*.* + + + + src/sample/java + samples + + *.java + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e9c21cf --- /dev/null +++ b/pom.xml @@ -0,0 +1,370 @@ + + + 4.0.0 + + qJava + Library providing connectivity between Java and kdb+. + + exxeleron + qJava + + 2014 + + bundle + 2.0.0-SNAPSHOT + + + exxeleron + http://www.exxeleron.com + + + ${project.organization.url} + + + + Apache License Version 2.0 + http://www.apache.org/licenses/ + + Copyright (c) 2011-2014 Exxeleron GmbH + + + + + + scm:git:https://github.com/exxeleron/qJava.git + https://github.com/exxeleron/qJava.git + + + + Github + https://github.com/exxeleron/qJava/issues + + + + Jenkins + + + + + + releases + releases + false + http://lib.devnet.de/libs-release-local + + + snapshots + snapshots + false + http://lib.devnet.de/libs-snapshot-local + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + true + + + org.apache.maven.plugins + maven-source-plugin + true + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin-version} + + + enforce-plugin-versions + + enforce + + + + + Best Practice is to always define plugin versions! + true + true + true + clean,deploy,site + + org.apache.maven.plugins:maven-eclipse-plugin + org.apache.maven.plugins:maven-reactor-plugin + + org.apache.maven.plugins:maven-enforcer-plugin,org.apache.maven.plugins:maven-idea-plugin + + + + + + + + org.jacoco + jacoco-maven-plugin + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + javadoc + + process-resources + + qJava ${project.version} API + + + + + + maven-assembly-plugin + + + assembly.xml + + + + + dist-assembly + package + + single + + + + + + org.apache.felix + maven-bundle-plugin + ${maven-bundle-plugin-version} + true + + + + com.exxeleron.qjava + + + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin-version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin-version} + + ${java.version} + ${java.version} + ${javac.optimize} + ${javac.verbose} + ${javac.debug} + ${file.encoding} + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin-version} + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin-version} + + + org.codehaus.mojo + build-helper-maven-plugin + ${maven-build-helper-maven-plugin} + + + parse-version + + parse-version + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin-version} + + 512m + + + + package + + jar + + package + + + + + org.apache.maven.plugins + maven-scm-plugin + ${maven-scm-plugin-version} + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven-assembly-plugin-version} + + + org.apache.maven.plugins + maven-release-plugin + ${maven-release-plugin-version} + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin-version} + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin-version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin-version} + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-dependency-plugin-version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin-version} + + + **/*IntegrationTest.java + **/*IT.java + + + **/*Test.java + **/Test*.java + + true + once + -Xmx1024m -XX:MaxPermSize=128M ${argLine} + true + + + + org.apache.maven.plugins + maven-archetype-plugin + ${maven-archetype-plugin-version} + + + org.apache.maven.plugins + maven-site-plugin + ${maven-site-plugin-version} + + + org.apache.maven.plugins + maven-reactor-plugin + ${maven-reactor-plugin-version} + + + org.apache.maven.plugins + maven-eclipse-plugin + ${maven-eclipse-plugin-version} + + true + true + + + + org.jacoco + jacoco-maven-plugin + ${maven-jacoco-maven-plugin-version} + + + + + + + UTF-8 + ISO-8859-1 + + 1.5 + false + true + false + + + 1.2 + 2.5 + 2.5.1 + 2.2 + 2.6 + 2.9 + 1.7 + 1.8 + 2.4 + 2.3.2 + 2.4 + 2.7 + 2.4 + 2.5.1 + 2.12.4 + 2.2 + 3.3 + 1.0 + 2.9 + 0.6.1.201212231917 + 2.4.0 + + + 4.11 + + + + + junit + junit + ${junit.version} + + + + diff --git a/src/main/java/com/exxeleron/qjava/DateTime.java b/src/main/java/com/exxeleron/qjava/DateTime.java new file mode 100644 index 0000000..d0a83db --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/DateTime.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.util.Date; + +/** + * Common interface for the q date/time types. + */ +public interface DateTime { + + /** + * Returns internal q representation. + * + * @return internal q representation + */ + public Object getValue(); + + /** + * Converts q date/time object to {@link java.util.Date} instance. + * + * @return {@link java.util.Date} representing q value. + */ + public Date toDateTime(); + +} diff --git a/src/main/java/com/exxeleron/qjava/QBasicConnection.java b/src/main/java/com/exxeleron/qjava/QBasicConnection.java new file mode 100644 index 0000000..669a278 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QBasicConnection.java @@ -0,0 +1,271 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + * Base connector class for interfacing with the kdb+ service. Provides methods for synchronous and asynchronous + * interaction. + */ +public class QBasicConnection implements QConnection { + + private final String host; + private final int port; + private final String username; + private final String password; + private final String encoding; + + protected int protocolVersion; + + protected Socket connection; + protected DataInputStream inputStream; + protected OutputStream outputStream; + protected QReader reader; + protected QWriter writer; + + /** + * Initializes a new {@link QBasicConnection} instance. + * + * @param host + * Host of remote q service + * @param port + * Port of remote q service + * @param username + * Username for remote authorization + * @param password + * Password for remote authorization + * @param encoding + * Encoding used for serialization/deserialization of string objects + */ + public QBasicConnection(final String host, final int port, final String username, final String password, final String encoding) { + this.host = host; + this.port = port; + this.username = username; + this.password = password; + this.encoding = encoding; + } + + /** + * Initializes a new {@link QBasicConnection} instance with encoding set to "ISO-8859-1". + * + * @param host + * Host of remote q service + * @param port + * Port of remote q service + * @param username + * Username for remote authorization + * @param password + * Password for remote authorization + */ + public QBasicConnection(final String host, final int port, final String username, final String password) { + this(host, port, username, password, "ISO-8859-1"); + } + + /** + * {@inheritDoc} + */ + public void open() throws IOException, QException { + if ( connection == null ) { + if ( host != null ) { + initSocket(); + initialize(); + + reader = new QReader(inputStream, encoding); + writer = new QWriter(outputStream, encoding, protocolVersion); + } else { + throw new QConnectionException("Host cannot be null"); + } + } + } + + private void initSocket() throws UnknownHostException, IOException { + connection = new Socket(host, port); + connection.setTcpNoDelay(true); + inputStream = new DataInputStream(connection.getInputStream()); + outputStream = connection.getOutputStream(); + } + + private void initialize() throws IOException, QException { + final String credentials = password != null ? String.format("%s:%s", username, password) : username; + byte[] request = (credentials + "\3\0").getBytes(encoding); + final byte[] response = new byte[2]; + + outputStream.write(request); + if ( inputStream.read(response, 0, 1) != 1 ) { + close(); + initSocket(); + + request = (credentials + "\0").getBytes(encoding); + outputStream.write(request); + if ( inputStream.read(response, 0, 1) != 1 ) { + throw new QConnectionException("Connection denied."); + } + } + + protocolVersion = Math.min(response[0], 3); + } + + /** + * {@inheritDoc} + */ + public void close() throws IOException { + if ( connection != null ) { + connection.close(); + connection = null; + } + } + + /** + * {@inheritDoc} + */ + public void reset() throws IOException, QException { + if ( connection != null ) { + connection.close(); + } + connection = null; + open(); + } + + /** + * {@inheritDoc} + */ + public boolean isConnected() { + return connection != null && connection.isConnected(); + } + + /** + * {@inheritDoc} + */ + public Object sync( final String query, final Object... parameters ) throws QException, IOException { + query(QConnection.MessageType.SYNC, query, parameters); + final QMessage response = reader.read(false); + + if ( response.getMessageType() == QConnection.MessageType.RESPONSE ) { + return response.getData(); + } else { + writer.write(new QException("nyi: qJava expected response message"), + response.getMessageType() == QConnection.MessageType.ASYNC ? QConnection.MessageType.ASYNC : QConnection.MessageType.RESPONSE); + throw new QReaderException("Received message of type: " + response.getMessageType() + " where response was expected"); + } + } + + /** + * {@inheritDoc} + */ + public void async( final String query, final Object... parameters ) throws QException, IOException { + query(QConnection.MessageType.ASYNC, query, parameters); + } + + /** + * {@inheritDoc} + */ + public int query( final QConnection.MessageType msgType, final String query, final Object... parameters ) throws QException, IOException { + if ( connection == null ) { + throw new IOException("Connection is not established."); + } + + if ( parameters.length > 8 ) { + throw new QWriterException("Too many parameters."); + } + + if ( parameters.length == 0 ) // simple string query + { + return writer.write(query.toCharArray(), msgType); + } else { + final Object[] request = new Object[parameters.length + 1]; + request[0] = query.toCharArray(); + + int i = 1; + for ( final Object param : parameters ) { + request[i++] = param; + } + + return writer.write(request, msgType); + } + } + + /** + * {@inheritDoc} + */ + public Object receive( final boolean dataOnly, final boolean raw ) throws IOException, QException { + return dataOnly ? reader.read(raw).getData() : reader.read(raw); + } + + /** + * {@inheritDoc} + */ + public Object receive() throws IOException, QException { + return receive(true, false); + } + + /** + * Returns a String that represents the current {@link QBasicConnection}. + * + * @return a String that represents the current {@link QBasicConnection} + */ + @Override + public String toString() { + return String.format(":%s:%s", getHost(), getPort()); + } + + /** + * {@inheritDoc} + */ + public String getHost() { + return host; + } + + /** + * {@inheritDoc} + */ + public int getPort() { + return port; + } + + /** + * {@inheritDoc} + */ + public String getUsername() { + return username; + } + + /** + * {@inheritDoc} + */ + public String getPassword() { + return password; + } + + /** + * {@inheritDoc} + */ + public String getEncoding() { + return encoding; + } + + /** + * {@inheritDoc} + */ + public int getProtocolVersion() { + return protocolVersion; + } + +} \ No newline at end of file diff --git a/src/main/java/com/exxeleron/qjava/QCallbackConnection.java b/src/main/java/com/exxeleron/qjava/QCallbackConnection.java new file mode 100644 index 0000000..a41662e --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QCallbackConnection.java @@ -0,0 +1,188 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * The {@link QCallbackConnection}, in addition to {@link QBasicConnection}, provides an internal thread-based mechanism + * for asynchronous subscription. + * + * Methods of {@link QCallbackConnection} are not thread safe. + */ +public class QCallbackConnection extends QBasicConnection { + + protected QListener messageListener; + protected Thread listenerThread; + final CopyOnWriteArraySet messagesListeners; + + /** + * Initializes a new QCallbackConnection instance. + * + * @param host + * Host of remote q service + * @param port + * Port of remote q service + * @param username + * Username for remote authorization + * @param password + * Password for remote authorization + * @param encoding + * Encoding used for serialization/deserialization of string objects + */ + public QCallbackConnection(final String host, final int port, final String username, final String password, final String encoding) { + super(host, port, username, password, encoding); + + this.messagesListeners = new CopyOnWriteArraySet(); + } + + /** + * Initializes a new QCallbackConnection instance. + * + * @param host + * Host of remote q service + * @param port + * Port of remote q service + * @param username + * Username for remote authorization + * @param password + * Password for remote authorization + */ + public QCallbackConnection(final String host, final int port, final String username, final String password) { + this(host, port, username, password, "ISO-8859-1"); + } + + /** + * {@inheritDoc} + */ + @Override + public void close() throws IOException { + if ( connection != null ) { + stopListener(); + messagesListeners.clear(); + } + super.close(); + } + + /** + * Spawns a new thread which listens for asynchronous messages from the remote q host. If a messageListener thread + * already exists, nothing happens. + */ + public synchronized void startListener() { + if ( messageListener == null ) { + messageListener = new QListener(); + listenerThread = new Thread(messageListener, "qJava-listener" + this.toString()); + listenerThread.start(); + } + } + + /** + * Spawns a new thread which listens for asynchronous messages from the remote q host. If a messageListener thread + * already exists, nothing happens. + * + * @param threadName + * listener thread name + * + */ + public synchronized void startListener( final String threadName ) { + if ( messageListener == null ) { + messageListener = new QListener(); + listenerThread = new Thread(messageListener, threadName); + listenerThread.start(); + } + } + + /** + * Indicates that a messageListener thread should stop. The messageListener thread is stopped after receiving next + * message from the remote q host. If a messageListener doesn't exists, nothing happens. + */ + public synchronized void stopListener() { + if ( messageListener != null ) { + messageListener.running = false; + messageListener = null; + try { + listenerThread.join(500); + } catch ( final InterruptedException e ) { + // ignore + } + } + } + + /** + * Registers messageListener so that it will receive {@link QMessage} when data from kdb+ has been received. + * + * @param listener + * a {@link QMessagesListener} to be registered + */ + public synchronized void addMessagesListener( final QMessagesListener listener ) { + messagesListeners.add(listener); + } + + /** + * Unregisters messageListener so that it will no longer receive {@link QMessage}. + * + * @param listener + * a {@link QMessagesListener} to be unregistered + */ + public synchronized void removeMessagesListener( final QMessagesListener listener ) { + messagesListeners.remove(listener); + } + + /** + * Support for reporting incoming messages from kdb+ services. + * + * @param message + * a message to be distributed among messages listeners + */ + protected void fireMessageReceivedEvent( final QMessage message ) { + for ( final QMessagesListener listener : messagesListeners ) { + listener.messageReceived(message); + } + } + + /** + * Support for reporting incoming messages from kdb+ services. + * + * @param message + * a message to be distributed among messages listeners + */ + protected void fireErrorReceivedEvent( final QErrorMessage message ) { + for ( final QMessagesListener listener : messagesListeners ) { + listener.errorReceived(message); + } + } + + class QListener implements Runnable { + + boolean running = true; + + public void run() { + while ( running && isConnected() ) { + try { + final QMessage message = reader.read(false); + fireMessageReceivedEvent(message); + } catch ( final QException e ) { + fireErrorReceivedEvent(new QErrorMessage(e)); + } catch ( final IOException e ) { + fireErrorReceivedEvent(new QErrorMessage(e)); + running = false; + break; + } + } + } + } +} diff --git a/src/main/java/com/exxeleron/qjava/QConnection.java b/src/main/java/com/exxeleron/qjava/QConnection.java new file mode 100644 index 0000000..e0bb76f --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QConnection.java @@ -0,0 +1,201 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.io.IOException; +import java.net.UnknownHostException; + +/** + * Interface for the q connector. + * + * @author dev123 + */ +public interface QConnection { + + /** + * Defines IPC message types. + * + * @author dev123 + */ + public static enum MessageType { + ASYNC, + SYNC, + RESPONSE; + + /** + * Factory method for creating enum based on IPC message byte. + * + * @param i + * byte indicating message type + * @return {@link MessageType} matching the byte + * + * @throws IllegalArgumentException + */ + public static MessageType getMessageType( byte i ) { + switch ( i ) { + case 0: + return ASYNC; + case 1: + return SYNC; + case 2: + return RESPONSE; + default: + throw new IllegalArgumentException("Unsupported message type."); + } + } + } + + /** + * Initializes connection with the remote q service. + * + * @throws IOException + * @throws UnknownHostException + * @throws QException + */ + public abstract void open() throws IOException, QException; + + /** + * Closes connection with the remote q service. + * + * @throws IOException + */ + public abstract void close() throws IOException; + + /** + * Reinitializes connection with the remote q service. + * + * @throws IOException + * @throws UnknownHostException + * @throws QException + */ + public abstract void reset() throws IOException, QException; + + /** + * Check whether connection with the remote q host has been established. Note that this function doesn't check + * whether the connection is still active. One has to use a heartbeat mechanism in order to check whether the + * connection is still active. + * + * @return true if connection with remote host is established, false otherwise + */ + public abstract boolean isConnected(); + + /** + * Executes a synchronous query against the remote q service. + * + * @param query + * Query to be executed + * @param parameters + * Additional parameters + * @return deserialized response from the remote q service + * @throws QException + * @throws IOException + */ + public abstract Object sync( String query, Object... parameters ) throws QException, IOException; + + /** + * Executes an asynchronous query against the remote q service. + * + * @param query + * Query to be executed + * @param parameters + * Additional parameters + * @throws QException + * @throws IOException + */ + public abstract void async( String query, Object... parameters ) throws QException, IOException; + + /** + * Reads next message from the remote q service. + * + * @param dataOnly + * if true returns only data part of the message, if false retuns data and + * message meta-information encapsulated in QMessage + * @param raw + * indicates whether message should be parsed to Java object or returned as an array of bytes + * @return deserialized response from the remote q service + * @throws IOException + * @throws QException + */ + public abstract Object receive( boolean dataOnly, boolean raw ) throws IOException, QException; + + /** + * Reads next message from the remote q service. + * + * @return deserialized response from the remote q service + * @throws IOException + * @throws QException + */ + public abstract Object receive() throws IOException, QException; + + /** + * Executes a query against the remote q service. Result of the query has to be retrieved by calling a receive + * method. + * + * @param msgType + * Indicates whether message should be synchronous or asynchronous + * @param query + * Query to be executed + * @param parameters + * Additional parameters + * @return size of the sent message + * @throws QException + * @throws IOException + */ + public abstract int query( final MessageType msgType, final String query, final Object... parameters ) throws QException, IOException; + + /** + * Returns the host of a remote q service. + * + * @return host of remote a q service + */ + public abstract String getHost(); + + /** + * Returns the port of a remote q service. + * + * @return post of remote a q service + */ + public abstract int getPort(); + + /** + * Returns username for remote authorization. + * + * @return username for remote authorization + */ + public abstract String getUsername(); + + /** + * Returns password for remote authorization. + * + * @return password for remote authorization + */ + public abstract String getPassword(); + + /** + * Returns encoding used for serialization/deserialization of string objects. + * + * @return encoding used for serialization/deserialization of string objects + */ + public abstract String getEncoding(); + + /** + * Retrives version of the IPC protocol for an established connection. + * + * @return protocol version + */ + public abstract int getProtocolVersion(); + +} \ No newline at end of file diff --git a/src/main/java/com/exxeleron/qjava/QConnectionException.java b/src/main/java/com/exxeleron/qjava/QConnectionException.java new file mode 100644 index 0000000..ec7aa33 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QConnectionException.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +/** + * Exception representing q connection error. + */ +public class QConnectionException extends QException { + + private static final long serialVersionUID = -6996779408402779829L; + + /** + * Constructs an {@link QConnectionException} with the specified detailed message and cause. + * + * @param message + * the detail message + * @param cause + * the cause + */ + public QConnectionException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs an {@link QConnectionException} with the specified detailed message. + * + * @param message + * the detail message + */ + public QConnectionException(final String message) { + super(message); + } + +} diff --git a/src/main/java/com/exxeleron/qjava/QDate.java b/src/main/java/com/exxeleron/qjava/QDate.java new file mode 100644 index 0000000..a93467a --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QDate.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Represents q date type. + */ +public final class QDate implements DateTime { + private static final String NULL_STR = "0Nd"; + + private static final DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd"); + + private Date datetime; + private final Integer value; + + /** + * Creates new {@link QDate} instance using specified q date value. + * + * @param value + * a count of days since 2000.01.01 + */ + public QDate(final Integer value) { + this.value = value; + } + + /** + * Creates new {@link QDate} instance using specified {@link Date}. + * + * @param datetime + * {@link Date} to be set + */ + public QDate(final Date datetime) { + this.datetime = datetime; + value = datetime == null ? Integer.MIN_VALUE : (int) (Utils.tzOffsetFromQ(datetime.getTime()) / Utils.DAY_MILLIS - 10957); + } + + /** + * Returns a count of days since 2000.01.01 + * + * @return raw q value + */ + public Integer getValue() { + return value; + } + + /** + * Converts {@link QDate} object to {@link Date} instance. + * + * @return {@link Date} representing q value. + */ + public Date toDateTime() { + if ( datetime == null && value != Integer.MIN_VALUE ) { + datetime = new Date(Utils.tzOffsetToQ(Utils.QEPOCH_MILLIS + Utils.DAY_MILLIS * value)); + } + return datetime; + } + + /** + * Returns a String that represents the current {@link QDate}. + * + * @return a String representation of the {@link QDate} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + final Date dt = toDateTime(); + return dt == null ? NULL_STR : getDateformat().format(dt); + } + + /** + * Indicates whether some other object is "equal to" this {@link QDate} . {@link QDate} objects are considered equal + * if the underlying q raw value is the same for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QDate) ) { + return false; + } + + return value.equals(((QDate) obj).getValue()); + } + + /** + * Returns a hash code value for this {@link QDate}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * Returns a {@link QDate} represented by a given string. + * + * @param date + * as {@link String} + * @return a {@link QDate} instance representing date. + * @throws IllegalArgumentException + * when date cannot be parsed + */ + public static QDate fromString( final String date ) { + try { + return date == null || date.length() == 0 || date.equals(NULL_STR) ? new QDate(Integer.MIN_VALUE) : new QDate(getDateformat().parse(date)); + } catch ( final Exception e ) { + throw new IllegalArgumentException("Cannot parse QDate from: " + date, e); + } + } + + private static synchronized DateFormat getDateformat() { + return dateFormat; + } +} diff --git a/src/main/java/com/exxeleron/qjava/QDateTime.java b/src/main/java/com/exxeleron/qjava/QDateTime.java new file mode 100644 index 0000000..3a1a413 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QDateTime.java @@ -0,0 +1,143 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Represents q datetime type. + */ +public final class QDateTime implements DateTime { + + private static final String NULL_STR = "0Nz"; + + private static final DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd'T'HH:mm:ss.SSS"); + + private Date datetime; + private Double value; + + /** + * Creates new {@link QDateTime} instance using specified q date value. + * + * @param value + * a fractional day count from midnight 2000.01.01 + */ + public QDateTime(final Double value) { + this.value = value; + } + + /** + * Creates new {@link QDateTime} instance using specified {@link Date}. + * + * @param datetime + * {@link Date} to be set + */ + public QDateTime(final Date datetime) { + this.datetime = datetime; + if ( datetime != null ) { + value = (double) (Utils.tzOffsetFromQ(datetime.getTime()) - Utils.QEPOCH_MILLIS) / Utils.DAY_MILLIS; + } else { + value = Double.NaN; + } + } + + /** + * Returns a fractional day count from midnight 2000.01.01. + * + * @return raw q value + */ + public Double getValue() { + return value; + } + + /** + * Converts {@link QDateTime} object to {@link Date} instance. + * + * @return {@link Date} representing q value. + */ + public Date toDateTime() { + if ( datetime == null && !Double.isNaN(value) ) { + datetime = new Date(Utils.tzOffsetToQ(Utils.QEPOCH_MILLIS + (long) (value * Utils.DAY_MILLIS))); + } + return datetime; + } + + /** + * Returns a String that represents the current {@link QDateTime}. + * + * @return a String representation of the {@link QDateTime} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + final Date dt = toDateTime(); + return dt == null ? NULL_STR : getDateformat().format(dt); + } + + /** + * Indicates whether some other object is "equal to" this date. {@link QDateTime} objects are considered equal if + * the underlying q raw value is the same for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QDateTime) ) { + return false; + } + + return value.equals(((QDateTime) obj).getValue()); + } + + /** + * Returns a hash code value for this {@link QDateTime}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * Returns a {@link QDateTime} represented by a given string. + * + * @param date + * as {@link String} + * @return a {@link QDateTime} instance representing date. + * @throws IllegalArgumentException + * when date cannot be parsed + */ + public static QDateTime fromString( final String date ) { + try { + return date == null || date.length() == 0 || date.equals(NULL_STR) ? new QDateTime(Double.NaN) : new QDateTime(getDateformat().parse(date)); + } catch ( final Exception e ) { + throw new IllegalArgumentException("Cannot parse QDateTime from: " + date, e); + } + } + + private static synchronized DateFormat getDateformat() { + return dateFormat; + } +} diff --git a/src/main/java/com/exxeleron/qjava/QDictionary.java b/src/main/java/com/exxeleron/qjava/QDictionary.java new file mode 100644 index 0000000..4b12270 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QDictionary.java @@ -0,0 +1,177 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.lang.reflect.Array; +import java.util.Iterator; + +/** + * Represents a q dictionary type. + */ +public final class QDictionary implements Iterable { + + private final Object keys; + private final Object values; + private final int length; + + /** + * Creates new {@link QDictionary} instance with given keys and values arrays. + * + * @param keys + * keys array + * @param values + * values array + * + * @throws IllegalArgumentException + */ + public QDictionary(final Object keys, final Object values) { + if ( keys == null || !keys.getClass().isArray() ) { + throw new IllegalArgumentException("Parameter: keys is not an array"); + } + if ( values == null || !values.getClass().isArray() ) { + throw new IllegalArgumentException("Parameter: values is not an array"); + } + length = Array.getLength(keys); + if ( length != Array.getLength(values) ) { + throw new IllegalArgumentException("Keys and value arrays cannot have different length"); + } + + this.keys = keys; + this.values = values; + } + + /** + * Returns array containing list of keys stored in the dictionary. + * + * @return an array of keys + */ + public Object getKeys() { + return keys; + } + + /** + * Returns array containing list of values stored in the dictionary. + * + * @return an array of values + */ + public Object getValues() { + return values; + } + + /** + *

+ * Returns an iterator over a key/value pairs stored in the dictionary. + *

+ * + *

+ * Note that the iterator returned by this method will throw an {@link UnsupportedOperationException} in response to + * its remove method. + *

+ * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return new Iterator() { + + private int index = 0; + + public boolean hasNext() { + return index < length; + } + + public KeyValuePair next() { + return new KeyValuePair(index++); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Defines a key/value pair that can be retrieved. + */ + public class KeyValuePair { + + private final int index; + + KeyValuePair(final int index) { + this.index = index; + } + + /** + * Returns key from given pair. + * + * @return key + */ + public Object getKey() { + return Array.get(keys, index); + } + + /** + * Returns value from given pair. + * + * @return value + */ + public Object getValue() { + return Array.get(values, index); + } + } + + /** + * Returns a String that represents the current {@link QDictionary}. + * + * @return a String representation of the {@link QDictionary} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "QDictionary: " + Utils.arrayToString(keys) + "!" + Utils.arrayToString(values); + } + + /** + * Indicates whether some other object is "equal to" this dictionary. {@link QDictionary} objects are considered + * equal if the keys and values lists are equal for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QDictionary) ) { + return false; + } + + final QDictionary d = (QDictionary) obj; + return Utils.deepArraysEquals(keys, d.keys) && Utils.deepArraysEquals(values, d.values); + } + + /** + * Returns a hash code value for this {@link QDictionary}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return 31 * Utils.arrayHashCode(keys) + Utils.arrayHashCode(values); + } +} diff --git a/src/main/java/com/exxeleron/qjava/QErrorMessage.java b/src/main/java/com/exxeleron/qjava/QErrorMessage.java new file mode 100644 index 0000000..f822269 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QErrorMessage.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +/** + * Encapsulates an error encountered during receiving data from kdb+. + * + * @author dev123 + */ +public final class QErrorMessage { + + private final Throwable cause; + + /** + * Creates new {@link QErrorMessage} object with specified cause. + * + * @param cause + */ + public QErrorMessage(final Throwable cause) { + this.cause = cause; + } + + /** + * Retrieves the source exception. + * + * @return the exception + */ + public Throwable getCause() { + return cause; + } + +} diff --git a/src/main/java/com/exxeleron/qjava/QException.java b/src/main/java/com/exxeleron/qjava/QException.java new file mode 100644 index 0000000..92adcc2 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QException.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +/** + * Exception representing q error. + */ +public class QException extends Exception { + + private static final long serialVersionUID = -3871929200321654473L; + + /** + * Constructs a {@link QException} with the specified detailed message and cause. + * + * @param message + * the detail message + * @param cause + * the cause + */ + public QException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a {@link QException} with the specified detailed message. + * + * @param message + * the detail message + */ + public QException(final String message) { + super(message); + } + +} diff --git a/src/main/java/com/exxeleron/qjava/QKeyedTable.java b/src/main/java/com/exxeleron/qjava/QKeyedTable.java new file mode 100644 index 0000000..ab3ec39 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QKeyedTable.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.util.Iterator; + +/** + * Represents a q keyed table type. + */ +public class QKeyedTable implements Iterable { + + private final QTable keys; + private final QTable values; + private final int length; + + /** + * Creates new {@link QKeyedTable} instance with given keys and values arrays. + * + * @param keys + * keys {@link QTable} + * @param values + * values {@link QTable} + * + * @throws IllegalArgumentException + */ + public QKeyedTable(final QTable keys, final QTable values) { + length = keys.getRowsCount(); + if ( length != values.getRowsCount() ) { + throw new IllegalArgumentException("Keys and value arrays cannot have different length"); + } + + this.keys = keys; + this.values = values; + } + + /** + * Returns {@link QTable} containing table keys. + * + * @return table of keys + */ + public QTable getKeys() { + return keys; + } + + /** + * Returns {@link QTable} containing table values. + * + * @return table of values + */ + public QTable getValues() { + return values; + } + + /** + *

+ * Returns an iterator over a key/value pairs stored in the keyed table. + *

+ * + *

+ * Note that the iterator returned by this method will throw an {@link UnsupportedOperationException} in response to + * its remove method. + *

+ * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return new Iterator() { + + private int index = 0; + + public boolean hasNext() { + return index < length; + } + + public KeyValuePair next() { + return new KeyValuePair(index++); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Defines a key/value pair that can be retrieved. + */ + public class KeyValuePair { + + private final int index; + + KeyValuePair(final int index) { + this.index = index; + } + + /** + * Returns key from given pair. + * + * @return key + */ + public QTable.Row getKey() { + return keys.get(index); + } + + /** + * Returns value from given pair. + * + * @return value + */ + public QTable.Row getValue() { + return values.get(index); + } + } + + /** + * Returns a String that represents the current {@link QKeyedTable}. + * + * @return a String representation of the {@link QKeyedTable} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "QKeyedTable: " + keys + "|" + values; + } + + /** + * Indicates whether some other object is "equal to" this keyed table. {@link QKeyedTable} objects are considered + * equal if the keys and values lists are equal for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QKeyedTable) ) { + return false; + } + + final QKeyedTable kt = (QKeyedTable) obj; + return keys.equals(kt.getKeys()) && values.equals(kt.getValues()); + } + + /** + * Returns a hash code value for this {@link QKeyedTable}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return 31 * keys.hashCode() + values.hashCode(); + } + +} diff --git a/src/main/java/com/exxeleron/qjava/QLambda.java b/src/main/java/com/exxeleron/qjava/QLambda.java new file mode 100644 index 0000000..a057099 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QLambda.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +/** + * Represents a q lambda expression. + */ +public final class QLambda { + private final String expression; + private final Object[] parameters; + + /** + * Gets body of a q lambda expression. + * + * @return body of a q lambda expression + */ + public String getExpression() { + return expression; + } + + /** + * Gets parameters of a q lambda expression. + * + * @return array containing lambda expression parameters + */ + public Object[] getParameters() { + return parameters; + } + + /** + * Creates new {@link QLambda} instance with given body and parameters. + * + * @param expression + * body of a q lambda expression + * @param parameters + * array containing lambda expression parameters + */ + public QLambda(final String expression, final Object[] parameters) { + if ( expression == null || expression.length() == 0 ) { + throw new IllegalArgumentException("Lambda expression cannot be null or empty"); + } + + this.expression = expression; + this.parameters = parameters; + } + + /** + * Creates new {@link QLambda} instance with given body and no parameters. + * + * @param expression + * body of a q lambda expression + */ + public QLambda(final String expression) { + this(expression, null); + } + + /** + * Returns a String that represents the current {@link QLambda}. + * + * @return a String representation of the {@link QLambda} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "QLambda: " + expression + (parameters == null ? "" : Utils.arrayToString(parameters)); + } + + /** + * Indicates whether some other object is "equal to" this lambda expression. {@link QLambda} objects are considered + * equal if the expression and parameters list are equal for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QLambda) ) { + return false; + } + + final QLambda l = (QLambda) obj; + return expression.equals(l.expression) && Utils.deepArraysEquals(parameters, l.parameters); + } + + /** + * Returns a hash code value for this {@link QLambda}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return expression.hashCode(); + } + +} diff --git a/src/main/java/com/exxeleron/qjava/QMessage.java b/src/main/java/com/exxeleron/qjava/QMessage.java new file mode 100644 index 0000000..b4628b6 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QMessage.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.nio.ByteOrder; + +/** + * Encapsulates the q message. + * + * @author dev123 + */ +public final class QMessage { + + private final ByteOrder endianess; + private final boolean compressed; + private final boolean raw; + private final QConnection.MessageType messageType; + private final int messageSize; + private final int dataSize; + + private final Object data; + + /** + * Creates new {@link QMessage} object. + * + * @param data + * data payload + * @param messageType + * type of the q message + * @param endianess + * endianess of the data + * @param compressed + * true if message was compressed, false otherwise + * @param raw + * true if raw message was retrieved, false if message was parsed + * @param messageSize + * size of the message + * @param dataSize + * size of the data payload section + */ + public QMessage(final Object data, final QConnection.MessageType messageType, final ByteOrder endianess, final boolean compressed, final boolean raw, + final int messageSize, final int dataSize) { + this.data = data; + this.messageType = messageType; + this.endianess = endianess; + this.compressed = compressed; + this.raw = raw; + this.messageSize = messageSize; + this.dataSize = dataSize; + } + + /** + * Retrieves the data payload associated with the message. + * + * @return the data + */ + public Object getData() { + return data; + } + + /** + * Indicates endianess of the message. + * + * @return endianess of the message + */ + public ByteOrder getEndianess() { + return endianess; + } + + /** + * Indicates whether message was compressed. + * + * @return true if message was compressed, false otherwise + */ + public boolean isCompressed() { + return compressed; + } + + /** + * Indicates whether message has been parsed. + * + * @return true if message is returned as a raw byte data, false otherwise + */ + public boolean isRaw() { + return raw; + } + + /** + * Gets type of the message. + * + * @return type of the message + */ + public QConnection.MessageType getMessageType() { + return messageType; + } + + /** + * Gets a total size of the message. + * + * @return total size of the message + */ + public int getMessageSize() { + return messageSize; + } + + /** + * Gets a size of data section in the message. + * + * @return size of data section in the message + */ + public int getDataSize() { + return dataSize; + } + +} \ No newline at end of file diff --git a/src/main/java/com/exxeleron/qjava/QMessagesListener.java b/src/main/java/com/exxeleron/qjava/QMessagesListener.java new file mode 100644 index 0000000..6034cad --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QMessagesListener.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.util.EventListener; + +/** + * Listener class for kdb+ subscription + */ +public interface QMessagesListener extends EventListener { + + /** + * Invoked when a new message has been received from a kdb+ server. + * + * @param message + * a {@link QMessage} containing received data + */ + public void messageReceived( QMessage message ); + + /** + * Invoked when an error has been encountered. + * + * @param message + * a {@link QErrorMessage} encapsulating error + */ + public void errorReceived( QErrorMessage message ); +} diff --git a/src/main/java/com/exxeleron/qjava/QMinute.java b/src/main/java/com/exxeleron/qjava/QMinute.java new file mode 100644 index 0000000..cadd3f2 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QMinute.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Represents q minute type. + */ +public final class QMinute implements DateTime { + + private static final String NULL_STR = "0Nu"; + + private static final DateFormat dateFormat = new SimpleDateFormat("HH:mm"); + + private Date datetime; + private Integer value; + + /** + * Creates new {@link QMinute} instance using specified q date value. + * + * @param value + * a count of minutes from midnight + */ + public QMinute(final Integer value) { + this.value = value; + } + + /** + * Creates new {@link QMinute} instance using specified {@link Date}. + * + * @param datetime + * {@link Date} to be set + */ + public QMinute(final Date datetime) { + this.datetime = datetime; + if ( datetime != null ) { + final Calendar c = Calendar.getInstance(); + c.setTime(datetime); + value = c.get(Calendar.MINUTE) + 60 * c.get(Calendar.HOUR_OF_DAY); + } else { + value = Integer.MIN_VALUE; + } + } + + /** + * Returns a count of minutes from midnight. + * + * @return raw q value + */ + public Integer getValue() { + return value; + } + + /** + * Converts {@link QMinute} object to {@link Date} instance. + * + * @return {@link Date} representing q value. + */ + public Date toDateTime() { + if ( datetime == null && value != Integer.MIN_VALUE ) { + final Calendar c = Calendar.getInstance(); + c.set(2000, 0, 1, value / 60, value % 60); + datetime = c.getTime(); + } + return datetime; + } + + /** + * Returns a String that represents the current {@link QMinute}. + * + * @return a String representation of the {@link QMinute} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + final Date dt = toDateTime(); + return dt == null ? NULL_STR : getDateformat().format(dt); + } + + /** + * Indicates whether some other object is "equal to" this date. {@link QMinute} objects are considered equal if the + * underlying q raw value is the same for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QMinute) ) { + return false; + } + + return value.equals(((QMinute) obj).getValue()); + } + + /** + * Returns a hash code value for this {@link QMinute}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * Returns a {@link QMinute} represented by a given string. + * + * @param date + * as {@link String} + * @return a {@link QMinute} instance representing date. + * @throws IllegalArgumentException + * when date cannot be parsed + */ + public static QMinute fromString( final String date ) { + try { + return date == null || date.length() == 0 || date.equals(NULL_STR) ? new QMinute(Integer.MIN_VALUE) : new QMinute(getDateformat().parse(date)); + } catch ( final Exception e ) { + throw new IllegalArgumentException("Cannot parse QMinute from: " + date, e); + } + } + + private static synchronized DateFormat getDateformat() { + return dateFormat; + } +} diff --git a/src/main/java/com/exxeleron/qjava/QMonth.java b/src/main/java/com/exxeleron/qjava/QMonth.java new file mode 100644 index 0000000..9b2de2a --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QMonth.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Represents q month type. + */ +public final class QMonth implements DateTime { + + private static final String NULL_STR = "0Nm"; + + private static final DateFormat dateFormat = new SimpleDateFormat("yyyy.MM'm'"); + + private Date datetime; + private Integer value; + + /** + * Creates new {@link QMonth} instance using specified q date value. + * + * @param value + * a count of months since 2000.01.01 + */ + public QMonth(final Integer value) { + this.value = value; + } + + /** + * Creates new {@link QMonth} instance using specified {@link Date}. + * + * @param datetime + * {@link Date} to be set + */ + public QMonth(final Date datetime) { + this.datetime = datetime; + if ( datetime != null ) { + final Calendar c = Calendar.getInstance(); + c.setTime(datetime); + value = 12 * (c.get(Calendar.YEAR) - 2000) + c.get(Calendar.MONTH); + } else { + value = Integer.MIN_VALUE; + } + } + + /** + * Returns a count of months since 2000.01.01. + * + * @return raw q value + */ + public Integer getValue() { + return value; + } + + /** + * Converts {@link QMonth} object to {@link Date} instance. + * + * @return {@link Date} representing q value. + */ + public Date toDateTime() { + if ( datetime == null && value != Integer.MIN_VALUE ) { + final Calendar c = Calendar.getInstance(); + c.set(2000 + value / 12, value % 12 + 1, 0, 0, 0); + datetime = c.getTime(); + } + return datetime; + } + + /** + * Returns a String that represents the current {@link QMonth}. + * + * @return a String representation of the {@link QMonth} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + final Date dt = toDateTime(); + return dt == null ? NULL_STR : getDateformat().format(dt); + } + + /** + * Indicates whether some other object is "equal to" this date. {@link QMonth} objects are considered equal if the + * underlying q raw value is the same for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QMonth) ) { + return false; + } + + return value.equals(((QMonth) obj).getValue()); + } + + /** + * Returns a hash code value for this {@link QMonth}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * Returns a {@link QMonth} represented by a given string. + * + * @param date + * as {@link String} + * @return a {@link QMonth} instance representing date. + * @throws IllegalArgumentException + * when date cannot be parsed + */ + public static QMonth fromString( final String date ) { + try { + return date == null || date.length() == 0 || date.equals(NULL_STR) ? new QMonth(Integer.MIN_VALUE) : new QMonth(getDateformat().parse(date)); + } catch ( final Exception e ) { + throw new IllegalArgumentException("Cannot parse QMonth from: " + date, e); + } + } + + private static synchronized DateFormat getDateformat() { + return dateFormat; + } +} diff --git a/src/main/java/com/exxeleron/qjava/QReader.java b/src/main/java/com/exxeleron/qjava/QReader.java new file mode 100644 index 0000000..e8c2d4e --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QReader.java @@ -0,0 +1,514 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.io.DataInputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.UUID; + +/** + * Provides deserialization from q IPC protocol.
+ * + * Methods of {@link QReader} are not thread safe. + */ +public final class QReader { + + private final static String PROTOCOL_DEBUG_ENV = "QJAVA_PROTOCOL_DEBUG"; + + private final DataInputStream stream; + private final String encoding; + private final ByteInputStream reader; + + private byte[] header; + private byte[] rawData; + + /** + * Initializes a new {@link QReader} instance. + * + * @param inputStream + * Input stream containing serialized messages + * @param encoding + * Encoding used for deserialization of string data + */ + public QReader(final DataInputStream inputStream, final String encoding) { + this.stream = inputStream; + this.encoding = encoding; + this.reader = new ByteInputStream(ByteOrder.nativeOrder()); + } + + /** + * Reads next message from the stream and returns a deserialized object. + * + * @param raw + * indicates whether reply should be parsed or return as raw data + * @return {@link QMessage} instance encapsulating a deserialized message. + * + * @throws IOException + * @throws QException + */ + public QMessage read( final boolean raw ) throws IOException, QException { + header = new byte[8]; + stream.readFully(header, 0, 8); + reader.wrap(header); + + final ByteOrder endianess = reader.get() == 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; + final QConnection.MessageType messageType = QConnection.MessageType.getMessageType(reader.get()); + final boolean compressed = reader.get() == 1; + reader.get(); // skip 1 byte + + reader.order(endianess); + final int messageSize = reader.getInt(); + int dataSize = Math.max(messageSize - 8, 0); + + // read message + rawData = new byte[dataSize]; + stream.readFully(rawData, 0, dataSize); + + if ( raw ) { + return new QMessage(rawData, messageType, endianess, compressed, raw, messageSize, dataSize); + } + + byte[] data = rawData; + if ( compressed ) { + data = uncompress(rawData, endianess); + dataSize = data.length; + } + + reader.wrap(data); + reader.order(endianess); + + try { + return new QMessage(readObject(), messageType, endianess, compressed, raw, messageSize, dataSize); + } catch ( final QReaderException e ) { + protocolDebug(e); + throw e; + } catch ( final RuntimeException e ) { + protocolDebug(e); + throw e; + } + } + + private void protocolDebug( final Exception e ) { + if ( System.getenv().containsKey(PROTOCOL_DEBUG_ENV) ) { + final String debugPath = System.getenv(PROTOCOL_DEBUG_ENV) + File.separator + PROTOCOL_DEBUG_ENV + "." + System.currentTimeMillis(); + PrintWriter out = null; + + try { + out = new PrintWriter(debugPath); + out.write(Utils.getHex(header)); + out.write(Utils.getHex(rawData)); + out.write("\n"); + e.printStackTrace(out); + } catch ( final Exception ex ) { + // ignore + } finally { + if ( out != null ) { + try { + out.close(); + } catch ( final Exception ex ) { + // ignore + } + } + } + } + } + + private byte[] uncompress( final byte[] compressedData, final ByteOrder endianess ) throws QException { + // size of the uncompressed message is encoded on first 4 bytes + // size has to be decreased by header length (8 bytes) + final ByteBuffer byteBuffer = ByteBuffer.wrap(compressedData, 0, 4); + byteBuffer.order(endianess); + final int uncompressedSize = -8 + byteBuffer.getInt(); + + if ( uncompressedSize <= 0 ) { + throw new QReaderException("Error while data uncompression."); + } + + final byte[] uncompressed = new byte[uncompressedSize]; + final int[] buffer = new int[256]; + short i = 0; + int n = 0, r = 0, f = 0, s = 0, p = 0, d = 4; + + while ( s < uncompressedSize ) { + if ( i == 0 ) { + f = 0xff & compressedData[d++]; + i = 1; + } + if ( (f & i) != 0 ) { + r = buffer[0xff & compressedData[d++]]; + uncompressed[s++] = uncompressed[r++]; + uncompressed[s++] = uncompressed[r++]; + n = 0xff & compressedData[d++]; + for ( int m = 0; m < n; m++ ) { + uncompressed[s + m] = uncompressed[r + m]; + } + } else { + uncompressed[s++] = compressedData[d++]; + } + while ( p < s - 1 ) { + buffer[(0xff & uncompressed[p]) ^ (0xff & uncompressed[p + 1])] = p++; + } + if ( (f & i) != 0 ) { + p = s += n; + } + i *= 2; + if ( i == 256 ) { + i = 0; + } + } + + return uncompressed; + } + + private Object readObject() throws QException, IOException { + final QType qtype = QType.getQType(reader.get()); + + if ( qtype == QType.GENERAL_LIST ) { + return readGeneralList(); + } else if ( qtype == QType.NULL_ITEM ) { + return null; + } else if ( qtype == QType.ERROR ) { + throw readError(); + } else if ( qtype == QType.DICTIONARY ) { + return readDictionary(); + } else if ( qtype == QType.TABLE ) { + return readTable(); + } else if ( qtype.getTypeCode() < 0 ) { + return readAtom(qtype); + } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { + return readList(qtype); + } else if ( qtype == QType.LAMBDA ) { + return readLambda(); + } else if ( qtype == QType.LAMBDA_PART ) { + return readLambdaPart(); + } + throw new QReaderException("Unable to deserialize q type: " + qtype); + } + + @SuppressWarnings("incomplete-switch") + private Object readAtom( final QType qtype ) throws QException, UnsupportedEncodingException { + switch ( qtype ) { + case BOOL: + return reader.get() == 1 ? true : false; + case GUID: + return readGuid(); + case BYTE: + return reader.get(); + case SHORT: + return reader.getShort(); + case INT: + return reader.getInt(); + case LONG: + return reader.getLong(); + case FLOAT: + return reader.getFloat(); + case DOUBLE: + return reader.getDouble(); + case CHAR: + return (char) reader.get(); + case SYMBOL: + return reader.getSymbol(); + case TIMESTAMP: + return new QTimestamp(reader.getLong()); + case MONTH: + return new QMonth(reader.getInt()); + case DATE: + return new QDate(reader.getInt()); + case DATETIME: + return new QDateTime(reader.getDouble()); + case TIMESPAN: + return new QTimespan(reader.getLong()); + case MINUTE: + return new QMinute(reader.getInt()); + case SECOND: + return new QSecond(reader.getInt()); + case TIME: + return new QTime(reader.getInt()); + } + + throw new QReaderException("Unable to deserialize q type: " + qtype); + } + + @SuppressWarnings("incomplete-switch") + private Object readList( final QType qtype ) throws QException, UnsupportedEncodingException { + reader.get(); // ignore attributes + final int length = reader.getInt(); + + switch ( qtype ) { + case BOOL_LIST: { + final boolean[] list = new boolean[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.get() == 1 ? true : false; + } + return list; + } + + case GUID_LIST: { + final UUID[] list = new UUID[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = readGuid(); + } + return list; + } + + case BYTE_LIST: { + final byte[] list = new byte[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.get(); + } + return list; + } + case SHORT_LIST: { + final short[] list = new short[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getShort(); + } + return list; + } + case INT_LIST: { + final int[] list = new int[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getInt(); + } + return list; + } + case LONG_LIST: { + final long[] list = new long[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getLong(); + } + return list; + } + case FLOAT_LIST: { + final float[] list = new float[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getFloat(); + } + return list; + } + case DOUBLE_LIST: { + final double[] list = new double[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getDouble(); + } + return list; + } + case STRING: { + final byte[] buffer = new byte[length]; + reader.get(buffer, 0, length); + return new String(buffer, encoding).toCharArray(); + } + case SYMBOL_LIST: { + final String[] list = new String[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getSymbol(); + } + return list; + } + case TIMESTAMP_LIST: { + final QTimestamp[] list = new QTimestamp[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QTimestamp(reader.getLong()); + } + return list; + } + case MONTH_LIST: { + final QMonth[] list = new QMonth[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QMonth(reader.getInt()); + } + return list; + } + case DATE_LIST: { + final QDate[] list = new QDate[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QDate(reader.getInt()); + } + return list; + } + case DATETIME_LIST: { + final QDateTime[] list = new QDateTime[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QDateTime(reader.getDouble()); + } + return list; + } + case TIMESPAN_LIST: { + final QTimespan[] list = new QTimespan[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QTimespan(reader.getLong()); + } + return list; + } + case MINUTE_LIST: { + final QMinute[] list = new QMinute[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QMinute(reader.getInt()); + } + return list; + } + case SECOND_LIST: { + final QSecond[] list = new QSecond[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QSecond(reader.getInt()); + } + return list; + } + case TIME_LIST: { + final QTime[] list = new QTime[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QTime(reader.getInt()); + } + return list; + } + } + + throw new QReaderException("Unable to deserialize q type: " + qtype); + } + + private UUID readGuid() { + final ByteOrder currentOrder = reader.order(); + reader.order(ByteOrder.BIG_ENDIAN); + final long l1 = reader.getLong(); + final long l2 = reader.getLong(); + reader.order(currentOrder); + return new UUID(l1, l2); + } + + private Object[] readGeneralList() throws QException, IOException { + reader.get(); // ignore attributes + final int length = reader.getInt(); + final Object[] list = new Object[length]; + + for ( int i = 0; i < length; i++ ) { + list[i] = readObject(); + } + + return list; + } + + private QException readError() throws IOException { + return new QException(reader.getSymbol()); + } + + private Object readDictionary() throws QException, IOException { + final Object keys = readObject(); + final Object values = readObject(); + + if ( keys != null && keys.getClass().isArray() && values != null && values.getClass().isArray() ) { + return new QDictionary(keys, values); + } else if ( keys instanceof QTable && values instanceof QTable ) { + return new QKeyedTable((QTable) keys, (QTable) values); + } + + throw new QReaderException("Cannot create valid dictionary object from mapping: " + keys + " to " + values); + } + + private QTable readTable() throws QException, IOException { + reader.get(); // attributes + reader.get(); // dict type stamp + return new QTable((String[]) readObject(), (Object[]) readObject()); + } + + private QLambda readLambda() throws QException, IOException { + reader.getSymbol(); + final char[] expression = (char[]) readObject(); + + return new QLambda(new String(expression)); + } + + private QLambda readLambdaPart() throws QException, IOException { + final int length = reader.getInt() - 1; + final QLambda lambda = readLambda(); + final Object[] parameters = new Object[length]; + for ( int i = 0; i < length; i++ ) { + parameters[i] = readObject(); + } + + return new QLambda(lambda.getExpression(), parameters); + } + + private final class ByteInputStream { + + private byte[] buffer; + private ByteOrder endianess; + private int position; + + public ByteInputStream(final ByteOrder endianess) { + this.endianess = endianess; + } + + protected void wrap( final byte[] newBuffer ) { + buffer = newBuffer; + position = 0; + } + + public void get( final byte[] dest, final int start, final int length ) { + System.arraycopy(buffer, position, dest, start, length); + position += length; + } + + public byte get() { + return buffer[position++]; + } + + public short getShort() { + final int x = buffer[position++], y = buffer[position++]; + return (short) (endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xff | y << 8 : x << 8 | y & 0xff); + } + + public int getInt() { + final int x = getShort(), y = getShort(); + return endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xffff | y << 16 : x << 16 | y & 0xffff; + } + + public long getLong() { + final int x = getInt(), y = getInt(); + return endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xffffffffL | (long) y << 32 : (long) x << 32 | y & 0xffffffffL; + } + + public float getFloat() { + return Float.intBitsToFloat(getInt()); + } + + public Double getDouble() { + return Double.longBitsToDouble(getLong()); + } + + public ByteOrder order() { + return endianess; + } + + public void order( @SuppressWarnings("hiding") final ByteOrder endianess ) { + this.endianess = endianess; + } + + private String getSymbol() throws UnsupportedEncodingException { + final int p = position; + + for ( ; buffer[position++] != 0; ) { + // empty; + } + return (p == position - 1) ? "" : new String(buffer, p, position - 1 - p, encoding); + } + } + +} diff --git a/src/main/java/com/exxeleron/qjava/QReaderException.java b/src/main/java/com/exxeleron/qjava/QReaderException.java new file mode 100644 index 0000000..7c9c704 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QReaderException.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +/** + * Exception representing q parsing error. + */ +public class QReaderException extends QException { + + private static final long serialVersionUID = -6996779408402779829L; + + /** + * Constructs a {@link QReaderException} with the specified detailed message and cause. + * + * @param message + * the detail message + * @param cause + * the cause + */ + public QReaderException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a {@link QReaderException} with the specified detailed message. + * + * @param message + * the detail message + */ + public QReaderException(final String message) { + super(message); + } + +} diff --git a/src/main/java/com/exxeleron/qjava/QSecond.java b/src/main/java/com/exxeleron/qjava/QSecond.java new file mode 100644 index 0000000..aabf6fd --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QSecond.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Represents q second type. + */ +public final class QSecond implements DateTime { + + private static final String NULL_STR = "0Nv"; + + private static final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); + + private Date datetime; + private Integer value; + + /** + * Creates new {@link QSecond} instance using specified q date value. + * + * @param value + * a count of seconds from midnight + */ + public QSecond(final Integer value) { + this.value = value; + } + + /** + * Creates new {@link QSecond} instance using specified {@link Date}. + * + * @param datetime + * {@link Date} to be set + */ + public QSecond(final Date datetime) { + this.datetime = datetime; + if ( datetime != null ) { + final Calendar c = Calendar.getInstance(); + c.setTime(datetime); + value = c.get(Calendar.SECOND) + c.get(Calendar.MINUTE) * 60 + 3600 * c.get(Calendar.HOUR_OF_DAY); + } else { + value = Integer.MIN_VALUE; + } + } + + /** + * Returns a count of seconds from midnight. + * + * @return raw q value + */ + public Integer getValue() { + return value; + } + + /** + * Converts {@link QSecond} object to {@link Date} instance. + * + * @return {@link Date} representing q value. + */ + public Date toDateTime() { + if ( datetime == null && value != Integer.MIN_VALUE ) { + final Calendar c = Calendar.getInstance(); + c.set(2000, 0, 1, value / 3600, (value / 60) % 60, value % 60); + datetime = c.getTime(); + } + return datetime; + } + + /** + * Returns a String that represents the current {@link QSecond}. + * + * @return a String representation of the {@link QSecond} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + final Date dt = toDateTime(); + return dt == null ? NULL_STR : getDateformat().format(dt); + } + + /** + * Indicates whether some other object is "equal to" this date. {@link QSecond} objects are considered equal if the + * underlying q raw value is the same for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QSecond) ) { + return false; + } + + return value.equals(((QSecond) obj).getValue()); + } + + /** + * Returns a hash code value for this {@link QSecond}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * Returns a {@link QSecond} represented by a given string. + * + * @param date + * as {@link String} + * @return a {@link QSecond} instance representing date. + * @throws IllegalArgumentException + * when date cannot be parsed + */ + public static QSecond fromString( final String date ) { + try { + return date == null || date.length() == 0 || date.equals(NULL_STR) ? new QSecond(Integer.MIN_VALUE) : new QSecond(getDateformat().parse(date)); + } catch ( final Exception e ) { + throw new IllegalArgumentException("Cannot parse QSecond from: " + date, e); + } + } + + private static synchronized DateFormat getDateformat() { + return dateFormat; + } +} diff --git a/src/main/java/com/exxeleron/qjava/QTable.java b/src/main/java/com/exxeleron/qjava/QTable.java new file mode 100644 index 0000000..2bcc5d1 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QTable.java @@ -0,0 +1,311 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.lang.reflect.Array; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * Represents a q table type. + */ +public final class QTable implements Iterable { + + private final String[] columns; + private final Object[] data; + private final int rowsCount; + private final Map columnsMap; + + /** + * Initializes a new instance of the {@link QTable} with specified column names and data matrix. + * + * @param columns + * column names + * @param data + * data matrix + * + * @throws IllegalArgumentException + */ + public QTable(final String[] columns, final Object[] data) { + if ( columns == null || columns.length == 0 ) { + throw new IllegalArgumentException("Columns array cannot be null or 0-length"); + } + if ( data == null || data.length == 0 ) { + throw new IllegalArgumentException("Data matrix cannot be null or 0-length"); + } + if ( columns.length != data.length ) { + throw new IllegalArgumentException("Columns array and data matrix cannot have different length"); + } + for ( final Object col : data ) { + if ( col == null || !col.getClass().isArray() ) { + throw new IllegalArgumentException("Non array column found in data matrix"); + } + } + + this.columnsMap = new HashMap(); + for ( int i = 0; i < columns.length; i++ ) { + this.columnsMap.put(columns[i], i); + } + + this.columns = columns; + this.data = data; + this.rowsCount = Array.getLength(data[0]); + } + + /** + * Gets a column index for specified name. + * + * @param column + * Name of the column + * @return 0 based column index + */ + public int getColumnIndex( final String column ) { + return columnsMap.get(column); + } + + /** + * Gets a number of rows in current {@link QTable}. + * + * @return a number of rows + */ + public int getRowsCount() { + return rowsCount; + } + + /** + * Gets a number of columns in current {@link QTable}. + * + * @return a number of columns + */ + public int getColumnsCount() { + return columns.length; + } + + /** + * Gets an array of columns in current {@link QTable}. + * + * @return an array of columns + */ + public String[] getColumns() { + return columns; + } + + /** + * Gets a data matrix in current {@link QTable}. + * + * @return an array of arrays with internal representation of data + */ + public Object[] getData() { + return data; + } + + /** + * Gets a row of data from current {@link QTable}. + * + * @param index + * 0 based row index + * @return Row object representing a row in current {@link QTable} + */ + public Row get( final int index ) { + return new Row(index); + } + + /** + * Returns a String that represents the current {@link QTable}. + * + * @return a String representation of the {@link QTable} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "QTable: " + Utils.arrayToString(columns) + "!" + Utils.arrayToString(data); + } + + /** + * Indicates whether some other object is "equal to" this table. {@link QTable} objects are considered equal if the + * columns and data matrix are equal for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QTable) ) { + return false; + } + + final QTable t = (QTable) obj; + return Utils.deepArraysEquals(columns, t.columns) && Utils.deepArraysEquals(data, t.data); + } + + /** + * Returns a hash code value for this {@link QTable}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return 31 * Utils.arrayHashCode(columns) + Utils.arrayHashCode(data); + } + + /** + *

+ * Returns an iterator over rows stored in the table. + *

+ * + *

+ * Note that the iterator returned by this method will throw an {@link UnsupportedOperationException} in response to + * its remove method. + *

+ * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return new Iterator() { + + private int index = 0; + + public boolean hasNext() { + return index < rowsCount; + } + + public Row next() { + if ( hasNext() ) { + return new Row(index++); + } else { + throw new NoSuchElementException(); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Represents single row in a table. + */ + public class Row implements Iterable { + + private final int rowIndex; + + /** + * Creates row view for row with given index. + * + * @param rowIndex + * index of the row + */ + public Row(final int rowIndex) { + if ( rowIndex < 0 || rowIndex > rowsCount ) { + throw new IndexOutOfBoundsException(); + } + this.rowIndex = rowIndex; + } + + /** + * Creates a copy of entire row and returns it as an {@link Object} array. + * + * @return {@link Object}[] with copy of entire row + */ + public Object[] toArray() { + final int length = getLength(); + final Object[] row = new Object[length]; + + for ( int i = 0; i < length; i++ ) { + row[i] = get(i); + } + + return row; + } + + /** + * Returns number of columns in the current {@link QTable}. + * + * @return number of columns + */ + public int getLength() { + return columns.length; + } + + /** + * Gets an object stored under specific index. + * + * @param index + * 0 based index + * @return object + */ + public Object get( final int index ) { + return Array.get(data[index], rowIndex); + } + + /** + * Sets an object stored under specific index. + * + * @param index + * 0 based index + * @param value + * value to be set + */ + public void set( final int index, final Object value ) { + Array.set(data[index], rowIndex, value); + } + + /** + *

+ * Returns an iterator over columns in a particular row in the table. + *

+ * + *

+ * Note that the iterator returned by this method will throw an {@link UnsupportedOperationException} in + * response to its remove method. + *

+ * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return new Iterator() { + + int index = 0; + + public boolean hasNext() { + return index < getLength(); + } + + public Object next() { + if ( hasNext() ) { + return Array.get(data[index++], rowIndex); + } else { + throw new NoSuchElementException(); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + } + +} diff --git a/src/main/java/com/exxeleron/qjava/QTime.java b/src/main/java/com/exxeleron/qjava/QTime.java new file mode 100644 index 0000000..7215c52 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QTime.java @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Represents q time type. + */ +public final class QTime implements DateTime { + + private static final String NULL_STR = "0Nt"; + + private static final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); + + private Date datetime; + private Integer value; + + /** + * Creates new {@link QTime} instance using specified q date value. + * + * @param value + * a count of milliseconds from midnight + */ + public QTime(final Integer value) { + this.value = value; + } + + /** + * Creates new {@link QTime} instance using specified {@link Date}. + * + * @param datetime + * {@link Date} to be set + */ + public QTime(final Date datetime) { + this.datetime = datetime; + if ( datetime != null ) { + final Calendar c = Calendar.getInstance(); + c.setTime(datetime); + value = c.get(Calendar.MILLISECOND) + 1000 * c.get(Calendar.SECOND) + 60000 * c.get(Calendar.MINUTE) + 3600000 * c.get(Calendar.HOUR_OF_DAY); + } else { + value = Integer.MIN_VALUE; + } + } + + /** + * Returns a count of milliseconds from midnight. + * + * @return raw q value + */ + public Integer getValue() { + return value; + } + + /** + * Converts {@link QTime} object to {@link Date} instance. + * + * @return {@link Date} representing q value. + */ + public Date toDateTime() { + if ( datetime == null && value != Integer.MIN_VALUE ) { + datetime = new Date(Utils.tzOffsetToQ(value + Utils.QEPOCH_MILLIS)); + } + return datetime; + } + + /** + * Returns a String that represents the current {@link QTime}. + * + * @return a String representation of the {@link QTime} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + final Date dt = toDateTime(); + return dt == null ? NULL_STR : getDateformat().format(dt); + } + + /** + * Indicates whether some other object is "equal to" this date. {@link QTime} objects are considered equal if the + * underlying q raw value is the same for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QTime) ) { + return false; + } + + return value.equals(((QTime) obj).getValue()); + } + + /** + * Returns a hash code value for this {@link QTime}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * Returns a {@link QTime} represented by a given string. + * + * @param date + * as {@link String} + * @return a {@link QTime} instance representing date. + * @throws IllegalArgumentException + * when date cannot be parsed + */ + public static QTime fromString( final String date ) { + try { + return date == null || date.length() == 0 || date.equals(NULL_STR) ? new QTime(Integer.MIN_VALUE) : new QTime(getDateformat().parse(date)); + } catch ( final Exception e ) { + throw new IllegalArgumentException("Cannot parse QTime from: " + date, e); + } + } + + private static synchronized DateFormat getDateformat() { + return dateFormat; + } +} diff --git a/src/main/java/com/exxeleron/qjava/QTimespan.java b/src/main/java/com/exxeleron/qjava/QTimespan.java new file mode 100644 index 0000000..e81ecb7 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QTimespan.java @@ -0,0 +1,166 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Represents q timespan type. + */ +public final class QTimespan implements DateTime { + private static final String NULL_STR = "0Nn"; + + private static final DateFormat dateFormat = new SimpleDateFormat("'D'HH:mm:ss.SSS"); + private static final NumberFormat nanosFormatter = new DecimalFormat("000000"); + private static final int NANOS_PER_SECOND = 1000000; + private static final long NANOS_PER_DAY = Utils.DAY_MILLIS * 1000000; + + private Date datetime; + private Long value; + + /** + * Creates new {@link QTimespan} instance using specified q date value. + * + * @param value + * a count of nanoseconds from midnight + */ + public QTimespan(final Long value) { + this.value = value; + } + + private static long getNanos( final Date datetime ) { + final Calendar c = Calendar.getInstance(); + c.setTime(datetime); + return (long) (c.get(Calendar.MILLISECOND) + 1000 * c.get(Calendar.SECOND) + 60000 * c.get(Calendar.MINUTE) + 3600000 * c.get(Calendar.HOUR_OF_DAY)) + * NANOS_PER_SECOND; + } + + /** + * Creates new {@link QTimespan} instance using specified {@link Date}. + * + * @param datetime + * {@link Date} to be set + */ + public QTimespan(final Date datetime) { + this.datetime = datetime; + if ( datetime != null ) { + value = getNanos(datetime); + } else { + value = Long.MIN_VALUE; + } + } + + /** + * Returns a count of nanoseconds from midnight. + * + * @return raw q value + */ + public Long getValue() { + return value; + } + + /** + * Converts {@link QTimespan} object to {@link Date} instance. + * + * @return {@link Date} representing q value. + */ + public Date toDateTime() { + if ( datetime == null && value != Long.MIN_VALUE ) { + datetime = new Date(Utils.tzOffsetToQ(Math.abs(value) / NANOS_PER_SECOND + Utils.QEPOCH_MILLIS)); + } + return datetime; + } + + /** + * Returns a String that represents the current {@link QTimespan}. + * + * @return a String representation of the {@link QTimespan} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + final Date dt = toDateTime(); + return dt == null ? NULL_STR : (value < 0 ? "-" : "") + (Math.abs(value) / NANOS_PER_DAY) + getDateformat().format(dt) + + nanosFormatter.format(Math.abs(value) % NANOS_PER_SECOND); + } + + /** + * Indicates whether some other object is "equal to" this date. {@link QTimespan} objects are considered equal if + * the underlying q raw value is the same for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QTimespan) ) { + return false; + } + + return value.equals(((QTimespan) obj).getValue()); + } + + /** + * Returns a hash code value for this {@link QTimespan}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * Returns a {@link QTimespan} represented by a given string. + * + * @param date + * as {@link String} + * @return a {@link QTimespan} instance representing date. + * @throws IllegalArgumentException + * when date cannot be parsed + */ + public static QTimespan fromString( final String date ) { + try { + if ( date == null || date.length() == 0 || date.equals(NULL_STR) ) { + return new QTimespan(Long.MIN_VALUE); + } else { + final long nanos = getNanos(getDateformat().parse(date.substring(date.indexOf("D"), date.lastIndexOf(".") + 3))) + + getNanosformat().parse(date.substring(date.lastIndexOf(".") + 3)).longValue(); + return new QTimespan(Integer.valueOf(date.substring(0, date.indexOf("D"))) * NANOS_PER_DAY + ('-' == date.charAt(0) ? -1 : 1) * nanos); + } + } catch ( final Exception e ) { + throw new IllegalArgumentException("Cannot parse QTimespan from: " + date, e); + } + } + + private static synchronized DateFormat getDateformat() { + return dateFormat; + } + + private static synchronized NumberFormat getNanosformat() { + return nanosFormatter; + } +} diff --git a/src/main/java/com/exxeleron/qjava/QTimestamp.java b/src/main/java/com/exxeleron/qjava/QTimestamp.java new file mode 100644 index 0000000..6ffa670 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QTimestamp.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Represents q timestamp type. + */ +public final class QTimestamp implements DateTime { + private static final String NULL_STR = "0Np"; + + private static final DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd'D'HH:mm:ss.SSS"); + private static final NumberFormat nanosFormatter = new DecimalFormat("000000"); + private static final int NANOS_PER_SECOND = 1000000; + + private Date datetime; + private Long value; + + /** + * Creates new {@link QTimestamp} instance using specified q date value. + * + * @param value + * a count of nanoseconds from midnight 2000.01.01 + */ + public QTimestamp(final Long value) { + this.value = value; + } + + private static long getNanos( final Date datetime ) { + return NANOS_PER_SECOND * (Utils.tzOffsetFromQ(datetime.getTime()) - Utils.QEPOCH_MILLIS); + } + + /** + * Creates new {@link QTimestamp} instance using specified {@link Date}. + * + * @param datetime + * {@link Date} to be set + */ + public QTimestamp(final Date datetime) { + this.datetime = datetime; + if ( datetime != null ) { + value = getNanos(datetime); + } else { + value = Long.MIN_VALUE; + } + } + + /** + * Returns a count of nanoseconds from midnight 2000.01.01. + * + * @return raw q value + */ + public Long getValue() { + return value; + } + + /** + * Converts {@link QTimestamp} object to {@link Date} instance. + * + * @return {@link Date} representing q value. + */ + public Date toDateTime() { + if ( datetime == null && value != Long.MIN_VALUE ) { + datetime = new Date(Utils.tzOffsetToQ(value / NANOS_PER_SECOND + Utils.QEPOCH_MILLIS)); + } + return datetime; + } + + /** + * Returns a String that represents the current {@link QTimestamp}. + * + * @return a String representation of the {@link QTimestamp} + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + final Date dt = toDateTime(); + return dt == null ? NULL_STR : getDateformat().format(dt) + nanosFormatter.format(value % NANOS_PER_SECOND); + + } + + /** + * Indicates whether some other object is "equal to" this date. {@link QTimestamp} objects are considered equal if + * the underlying q raw value is the same for both instances. + * + * @return true if this object is the same as the obj argument, false otherwise. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals( final Object obj ) { + if ( this == obj ) { + return true; + } + + if ( !(obj instanceof QTimestamp) ) { + return false; + } + + return value.equals(((QTimestamp) obj).getValue()); + } + + /** + * Returns a hash code value for this {@link QTimestamp}. + * + * @return a hash code value for this object + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * Returns a {@link QTimestamp} represented by a given string. + * + * @param date + * as {@link String} + * @return a {@link QTimestamp} instance representing date. + * @throws IllegalArgumentException + * when date cannot be parsed + */ + public static QTimestamp fromString( final String date ) { + try { + + if ( date == null || date.length() == 0 || date.equals(NULL_STR) ) { + return new QTimestamp(Long.MIN_VALUE); + } else { + return new QTimestamp(getNanos(getDateformat().parse(date.substring(0, date.lastIndexOf(".") + 3))) + + getNanosformat().parse(date.substring(date.lastIndexOf(".") + 3)).longValue()); + } + } catch ( final Exception e ) { + throw new IllegalArgumentException("Cannot parse QTimestamp from: " + date, e); + } + } + + private static synchronized DateFormat getDateformat() { + return dateFormat; + } + + private static synchronized NumberFormat getNanosformat() { + return nanosFormatter; + } +} diff --git a/src/main/java/com/exxeleron/qjava/QType.java b/src/main/java/com/exxeleron/qjava/QType.java new file mode 100644 index 0000000..869b841 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QType.java @@ -0,0 +1,292 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Utility class for conversions between q and Java types. + */ +public enum QType { + NULL_ITEM(101), + ERROR(-128), + GENERAL_LIST(0), + BOOL(-1), + BOOL_LIST(1), + GUID(-2), + GUID_LIST(2), + BYTE(-4), + BYTE_LIST(4), + SHORT(-5), + SHORT_LIST(5), + INT(-6), + INT_LIST(6), + LONG(-7), + LONG_LIST(7), + FLOAT(-8), + FLOAT_LIST(8), + DOUBLE(-9), + DOUBLE_LIST(9), + CHAR(-10), + STRING(10), + SYMBOL(-11), + SYMBOL_LIST(11), + TIMESTAMP(-12), + TIMESTAMP_LIST(12), + MONTH(-13), + MONTH_LIST(13), + DATE(-14), + DATE_LIST(14), + DATETIME(-15), + DATETIME_LIST(15), + TIMESPAN(-16), + TIMESPAN_LIST(16), + MINUTE(-17), + MINUTE_LIST(17), + SECOND(-18), + SECOND_LIST(18), + TIME(-19), + TIME_LIST(19), + LAMBDA(100), + LAMBDA_PART(104), + TABLE(98), + KEYED_TABLE(99), + DICTIONARY(99); + + QType(final int code) { + this.code = (byte) code; + } + + private byte code; + + byte getTypeCode() { + return code; + } + + @SuppressWarnings("rawtypes") + private static final Map toQ = Collections.unmodifiableMap(new HashMap() { + private static final long serialVersionUID = 7199217298785029447L; + + { + put(Object[].class, GENERAL_LIST); + put(Boolean.class, BOOL); + put(boolean[].class, BOOL_LIST); + put(Boolean[].class, BOOL_LIST); + put(Byte.class, BYTE); + put(byte[].class, BYTE_LIST); + put(Byte[].class, BYTE_LIST); + put(UUID.class, GUID); + put(UUID[].class, GUID_LIST); + put(Short.class, SHORT); + put(short[].class, SHORT_LIST); + put(Short[].class, SHORT_LIST); + put(Integer.class, INT); + put(int[].class, INT_LIST); + put(Integer[].class, INT_LIST); + put(Long.class, LONG); + put(long[].class, LONG_LIST); + put(Long[].class, LONG_LIST); + put(Float.class, FLOAT); + put(float[].class, FLOAT_LIST); + put(Float[].class, FLOAT_LIST); + put(Double.class, DOUBLE); + put(double[].class, DOUBLE_LIST); + put(Double[].class, DOUBLE_LIST); + put(Character.class, CHAR); + put(char[].class, STRING); + put(String.class, SYMBOL); + put(String[].class, SYMBOL_LIST); + put(QTimestamp.class, TIMESTAMP); + put(QTimestamp[].class, TIMESTAMP_LIST); + put(QMonth.class, MONTH); + put(QMonth[].class, MONTH_LIST); + put(QDate.class, DATE); + put(QDate[].class, DATE_LIST); + put(QDateTime.class, DATETIME); + put(QDateTime[].class, DATETIME_LIST); + put(QTimespan.class, TIMESPAN); + put(QTimespan[].class, TIMESPAN_LIST); + put(QMinute.class, MINUTE); + put(QMinute[].class, MINUTE_LIST); + put(QSecond.class, SECOND); + put(QSecond[].class, SECOND_LIST); + put(QTime.class, TIME); + put(QTime[].class, TIME_LIST); + put(QException.class, ERROR); + put(QDictionary.class, DICTIONARY); + put(QTable.class, TABLE); + put(QKeyedTable.class, KEYED_TABLE); + put(QLambda.class, LAMBDA); + } + }); + + @SuppressWarnings("rawtypes") + private static final Map fromQ = Collections.unmodifiableMap(new HashMap() { + private static final long serialVersionUID = 7199217298785029445L; + + { + put(GENERAL_LIST, Object[].class); + put(BOOL, Boolean.class); + put(BOOL_LIST, boolean[].class); + put(BYTE, Byte.class); + put(BYTE_LIST, byte[].class); + put(GUID, UUID.class); + put(GUID_LIST, UUID[].class); + put(SHORT, Short.class); + put(SHORT_LIST, short[].class); + put(INT, Integer.class); + put(INT_LIST, int[].class); + put(LONG, Long.class); + put(LONG_LIST, long[].class); + put(FLOAT, Float.class); + put(FLOAT_LIST, float[].class); + put(DOUBLE, Double.class); + put(DOUBLE_LIST, double[].class); + put(CHAR, Character.class); + put(STRING, char[].class); + put(SYMBOL, String.class); + put(SYMBOL_LIST, String[].class); + put(TIMESTAMP, QTimestamp.class); + put(TIMESTAMP_LIST, QTimestamp[].class); + put(MONTH, QMonth.class); + put(MONTH_LIST, QMonth[].class); + put(DATE, QDate.class); + put(DATE_LIST, QDate[].class); + put(DATETIME, QDateTime.class); + put(DATETIME_LIST, QDateTime[].class); + put(TIMESPAN, QTimespan.class); + put(TIMESPAN_LIST, QTimespan[].class); + put(MINUTE, QMinute.class); + put(MINUTE_LIST, QMinute[].class); + put(SECOND, QSecond.class); + put(SECOND_LIST, QSecond[].class); + put(TIME, QTime.class); + put(TIME_LIST, QTime[].class); + put(ERROR, QException.class); + put(DICTIONARY, QDictionary.class); + put(TABLE, QTable.class); + put(KEYED_TABLE, QKeyedTable.class); + put(LAMBDA, QLambda.class); + put(LAMBDA_PART, QLambda.class); + } + }); + + private static final Map qNulls = Collections.unmodifiableMap(new HashMap() { + private static final long serialVersionUID = 7199217298785029443L; + + { + put(BOOL, false); + put(BYTE, (byte) 0); + put(GUID, new UUID(0, 0)); + put(SHORT, Short.MIN_VALUE); + put(INT, Integer.MIN_VALUE); + put(LONG, Long.MIN_VALUE); + put(FLOAT, Float.NaN); + put(DOUBLE, Double.NaN); + put(CHAR, ' '); + put(SYMBOL, ""); + put(TIMESTAMP, new QTimestamp(Long.MIN_VALUE)); + put(MONTH, new QMonth(Integer.MIN_VALUE)); + put(DATE, new QDate(Integer.MIN_VALUE)); + put(DATETIME, new QDateTime(Double.NaN)); + put(TIMESPAN, new QTimespan(Long.MIN_VALUE)); + put(MINUTE, new QMinute(Integer.MIN_VALUE)); + put(SECOND, new QSecond(Integer.MIN_VALUE)); + put(TIME, new QTime(Integer.MIN_VALUE)); + } + }); + + private static final Map lookup = Collections.unmodifiableMap(new HashMap() { + private static final long serialVersionUID = 7199217298785029441L; + + { + for ( final QType qtype : EnumSet.allOf(QType.class) ) { + put(qtype.getTypeCode(), qtype); + } + } + + }); + + /** + * Returns {@link QType} based on type code identifier. + * + * @param typecode + * type code identifier + * @return {@link QType} enum bound with type code identifier + * @throws QReaderException + */ + public static QType getQType( final Byte typecode ) throws QReaderException { + if ( lookup.containsKey(typecode) ) { + return lookup.get(typecode); + } else { + throw new QReaderException("Cannot deserialize object of type: " + typecode); + } + } + + /** + * Returns default mapping for particular java object to representative q type. + * + * @param obj + * Requested object + * @return {@link QType} enum being a result of q serialization + * @throws QWriterException + */ + public static QType getQType( final Object obj ) throws QWriterException { + if ( obj == null ) { + return QType.NULL_ITEM; + } else if ( toQ.containsKey(obj.getClass()) ) { + return toQ.get(obj.getClass()); + } else { + throw new QWriterException("Cannot serialize object of type: " + obj.getClass().getCanonicalName()); + } + } + + /** + * Returns default mapping for particular q type. + * + * @param type + * Requested q type + * @return type of the object being a result of q message deserialization + * @throws QReaderException + */ + public static Class getType( final QType type ) throws QReaderException { + if ( fromQ.containsKey(type) ) { + return fromQ.get(type); + } else { + throw new QReaderException("Cannot deserialize object of type: " + type); + } + } + + /** + * Returns object representing q null of particular type. + * + * @param type + * Requested null type + * @return object representing q null + * @throws QException + */ + public static Object getQNull( final QType type ) throws QException { + if ( qNulls.containsKey(type) ) { + return qNulls.get(type); + } else { + throw new QException("Cannot find null value of type: " + type); + } + } +} diff --git a/src/main/java/com/exxeleron/qjava/QWriter.java b/src/main/java/com/exxeleron/qjava/QWriter.java new file mode 100644 index 0000000..498d916 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QWriter.java @@ -0,0 +1,614 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.util.UUID; + +/** + * Provides serialization to q IPC protocol.
+ * + * Methods of {@link QWriter} are not thread safe. + */ +public final class QWriter { + + private final OutputStream stream; + private ByteOutputStream writer; + private final String encoding; + + private int messageSize; + private final int protocolVersion; + + /** + * Initializes a new {@link QWriter} instance. + * + * @param outputStream + * Output stream for serialized messages + * @param encoding + * Encoding used for serialization of string data + * @param protocolVersion + * kdb+ protocol version + */ + public QWriter(final OutputStream outputStream, final String encoding, int protocolVersion) { + this.stream = outputStream; + this.encoding = encoding; + this.protocolVersion = protocolVersion; + } + + /** + * Serializes object to q IPC protocol and writes as a message to the output stream. + * + * @param obj + * Object to be serialized + * @param msgType + * Message type + * @return total size of the message, includes header (8 bytes) and data payload + * @throws IOException + * @throws QException + */ + public int write( final Object obj, final QConnection.MessageType msgType ) throws IOException, QException { + // serialize object + writer = new ByteOutputStream(calculateDataSize(obj)); + writeObject(obj); + messageSize = writer.size() + 8; + + // write header + @SuppressWarnings("resource") + final ByteOutputStream header = new ByteOutputStream(8); + header.write((byte) 1); // endianness + header.write((byte) msgType.ordinal()); + header.writeShort((short) 0); + header.writeInt(messageSize); + + // write message + stream.write(header.buffer, 0, 8); + stream.write(writer.buffer, 0, writer.count); + + return messageSize; + } + + private static int[] atomSize = { 0, 1, 16, 0, 1, 2, 4, 8, 4, 8, 1, 0, 8, 4, 4, 8, 8, 4, 4, 4 }; + + /** + * Calculates approximation of the object size + */ + private int calculateDataSize( final Object obj ) throws QException { + final QType qtype = QType.getQType(obj); + if ( qtype == QType.KEYED_TABLE ) { + return 1 + calculateDataSize(((QKeyedTable) obj).getKeys()) + calculateDataSize(((QKeyedTable) obj).getValues()); + } else if ( qtype == QType.TABLE ) { + return 3 + calculateDataSize(((QTable) obj).getColumns()) + calculateDataSize(((QTable) obj).getData()); + } else if ( qtype == QType.DICTIONARY ) { + return 1 + calculateDataSize(((QDictionary) obj).getKeys()) + calculateDataSize(((QDictionary) obj).getValues()); + } else if ( qtype == QType.SYMBOL ) { + // approximation - actual string encoding is an expensive operation + return 2 + 2 * ((String) obj).length(); + } else if ( qtype.getTypeCode() <= QType.BOOL.getTypeCode() && qtype.getTypeCode() >= QType.TIME.getTypeCode() ) { + return 1 + atomSize[-qtype.getTypeCode()]; + } else if ( qtype == QType.GENERAL_LIST ) { + final Object[] list = (Object[]) obj; + int size = 6; + for ( final Object object : list ) { + size += calculateDataSize(object); + } + return size; + } else if ( qtype == QType.SYMBOL_LIST ) { + final String[] list = (String[]) obj; + int size = 6; + for ( final String object : list ) { + // approximation - actual string encoding is an expensive + // operation + size += 1 + 2 * object.length(); + } + return size; + } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { + return 6 + atomSize[qtype.getTypeCode()] * Array.getLength(obj); + } + + return ByteOutputStream.BUFFER_SIZE; + } + + private void writeObject( final Object obj ) throws IOException, QException { + final QType qtype = QType.getQType(obj); + + if ( qtype == QType.STRING ) { + writeString((char[]) obj); + } else if ( qtype == QType.GENERAL_LIST ) { + writeGeneralList((Object[]) obj); + } else if ( qtype == QType.NULL_ITEM ) { + writeNullItem(); + } else if ( qtype == QType.ERROR ) { + writeError((Exception) obj); + } else if ( qtype == QType.DICTIONARY ) { + writeDictionary((QDictionary) obj); + } else if ( qtype == QType.TABLE ) { + writeTable((QTable) obj); + } else if ( qtype == QType.KEYED_TABLE ) { + writeKeyedTable((QKeyedTable) obj); + } else if ( qtype.getTypeCode() < 0 ) { + writeAtom(obj, qtype); + } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { + writeList(obj, qtype); + } else if ( qtype == QType.LAMBDA ) { + writeLambda((QLambda) obj); + } else { + throw new QWriterException("Unable to serialize q type: " + qtype); + } + } + + @SuppressWarnings("incomplete-switch") + private void writeAtom( final Object obj, final QType qtype ) throws IOException, QException { + writer.writeByte(qtype.getTypeCode()); + switch ( qtype ) { + case BOOL: + writer.writeByte((byte) ((Boolean) obj ? 1 : 0)); + break; + case GUID: + writeGuid((UUID) obj); + break; + case BYTE: + writer.writeByte((Byte) obj); + break; + case SHORT: + writer.writeShort((Short) obj); + break; + case INT: + writer.writeInt((Integer) obj); + break; + case LONG: + writer.writeLong((Long) obj); + break; + case FLOAT: + writer.writeFloat((Float) obj); + break; + case DOUBLE: + writer.writeDouble((Double) obj); + break; + case CHAR: + writer.writeByte((byte) (char) (Character) obj); + break; + case SYMBOL: + writeSymbol((String) obj); + break; + case TIMESTAMP: + if ( protocolVersion < 1 ) { + throw new QWriterException("kdb+ protocol version violation: timestamp not supported pre kdb+ v2.6"); + } + writer.writeLong(((QTimestamp) obj).getValue()); + break; + case MONTH: + writer.writeInt(((QMonth) obj).getValue()); + break; + case DATE: + writer.writeInt(((QDate) obj).getValue()); + break; + case DATETIME: + writer.writeDouble(((QDateTime) obj).getValue()); + break; + case TIMESPAN: + if ( protocolVersion < 1 ) { + throw new QWriterException("kdb+ protocol version violation: timespan not supported pre kdb+ v2.6"); + } + writer.writeLong(((QTimespan) obj).getValue()); + break; + case MINUTE: + writer.writeInt(((QMinute) obj).getValue()); + break; + case SECOND: + writer.writeInt(((QSecond) obj).getValue()); + break; + case TIME: + writer.writeInt(((QTime) obj).getValue()); + break; + } + } + + @SuppressWarnings("incomplete-switch") + private void writeList( final Object obj, final QType qtype ) throws IOException, QException { + writer.writeByte(qtype.getTypeCode()); + writer.writeByte((byte) 0); // attributes + + switch ( qtype ) { + case BOOL_LIST: { + if ( obj instanceof boolean[] ) { + final boolean[] list = (boolean[]) obj; + writer.writeInt(list.length); + for ( final boolean a : list ) { + writer.writeByte((byte) (a ? 1 : 0)); + } + } else if ( obj instanceof Boolean[] ) { + final Boolean[] list = (Boolean[]) obj; + writer.writeInt(list.length); + for ( final Boolean a : list ) { + writer.writeByte((byte) (a ? 1 : 0)); + } + } + break; + } + case GUID_LIST: { + final UUID[] list = (UUID[]) obj; + writer.writeInt(list.length); + for ( final UUID a : list ) { + writeGuid(a); + } + break; + } + case BYTE_LIST: { + if ( obj instanceof byte[] ) { + final byte[] list = (byte[]) obj; + writer.writeInt(list.length); + for ( final byte a : list ) { + writer.writeByte(a); + } + } else if ( obj instanceof Byte[] ) { + final Byte[] list = (Byte[]) obj; + writer.writeInt(list.length); + for ( final Byte a : list ) { + writer.writeByte(a); + } + } + break; + } + case SHORT_LIST: { + if ( obj instanceof short[] ) { + final short[] list = (short[]) obj; + writer.writeInt(list.length); + for ( final short a : list ) { + writer.writeShort(a); + } + } else if ( obj instanceof Short[] ) { + final Short[] list = (Short[]) obj; + writer.writeInt(list.length); + for ( final Short a : list ) { + writer.writeShort(a); + } + } + break; + } + case INT_LIST: { + if ( obj instanceof int[] ) { + final int[] list = (int[]) obj; + writer.writeInt(list.length); + for ( final int a : list ) { + writer.writeInt(a); + } + } else if ( obj instanceof Integer[] ) { + final Integer[] list = (Integer[]) obj; + writer.writeInt(list.length); + for ( final Integer a : list ) { + writer.writeInt(a); + } + } + break; + } + case LONG_LIST: { + if ( obj instanceof long[] ) { + final long[] list = (long[]) obj; + writer.writeInt(list.length); + for ( final long a : list ) { + writer.writeLong(a); + } + } else if ( obj instanceof Long[] ) { + final Long[] list = (Long[]) obj; + writer.writeInt(list.length); + for ( final Long a : list ) { + writer.writeLong(a); + } + } + break; + } + case FLOAT_LIST: { + if ( obj instanceof float[] ) { + final float[] list = (float[]) obj; + writer.writeInt(list.length); + for ( final float a : list ) { + writer.writeFloat(a); + } + } else if ( obj instanceof Float[] ) { + final Float[] list = (Float[]) obj; + writer.writeInt(list.length); + for ( final Float a : list ) { + writer.writeFloat(a); + } + } + break; + } + case DOUBLE_LIST: { + if ( obj instanceof double[] ) { + final double[] list = (double[]) obj; + writer.writeInt(list.length); + for ( final double a : list ) { + writer.writeDouble(a); + } + } else if ( obj instanceof Double[] ) { + final Double[] list = (Double[]) obj; + writer.writeInt(list.length); + for ( final Double a : list ) { + writer.writeDouble(a); + } + } + break; + } + case SYMBOL_LIST: { + final String[] list = (String[]) obj; + writer.writeInt(list.length); + for ( final String a : list ) { + writeSymbol(a); + } + break; + } + case TIMESTAMP_LIST: { + if ( protocolVersion < 1 ) { + throw new QWriterException("kdb+ protocol version violation: timestamp not supported pre kdb+ v2.6"); + } + final QTimestamp[] list = (QTimestamp[]) obj; + writer.writeInt(list.length); + for ( final QTimestamp a : list ) { + writer.writeLong(a.getValue()); + } + break; + } + case MONTH_LIST: { + final QMonth[] list = (QMonth[]) obj; + writer.writeInt(list.length); + for ( final QMonth a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + case DATE_LIST: { + final QDate[] list = (QDate[]) obj; + writer.writeInt(list.length); + for ( final QDate a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + case DATETIME_LIST: { + final QDateTime[] list = (QDateTime[]) obj; + writer.writeInt(list.length); + for ( final QDateTime a : list ) { + writer.writeDouble(a.getValue()); + } + break; + } + case TIMESPAN_LIST: { + if ( protocolVersion < 1 ) { + throw new QWriterException("kdb+ protocol version violation: timespan not supported pre kdb+ v2.6"); + } + final QTimespan[] list = (QTimespan[]) obj; + writer.writeInt(list.length); + for ( final QTimespan a : list ) { + writer.writeLong(a.getValue()); + } + break; + } + case MINUTE_LIST: { + final QMinute[] list = (QMinute[]) obj; + writer.writeInt(list.length); + for ( final QMinute a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + case SECOND_LIST: { + final QSecond[] list = (QSecond[]) obj; + writer.writeInt(list.length); + for ( final QSecond a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + case TIME_LIST: { + final QTime[] list = (QTime[]) obj; + writer.writeInt(list.length); + for ( final QTime a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + } + } + + private void writeGeneralList( final Object[] list ) throws IOException, QException { + writer.writeByte(QType.GENERAL_LIST.getTypeCode()); + writer.writeByte((byte) 0); // attributes + writer.writeInt(list.length); + for ( final Object obj : list ) { + writeObject(obj); + } + } + + private void writeSymbol( final String s ) throws IOException { + writer.write(s.getBytes(encoding)); + writer.writeByte((byte) 0); + } + + private void writeGuid( final UUID obj ) throws QException { + if ( protocolVersion < 3 ) { + throw new QWriterException("kdb+ protocol version violation: Guid not supported pre kdb+ v3.0"); + } + + writer.writeLongBigEndian(obj.getMostSignificantBits()); + writer.writeLongBigEndian(obj.getLeastSignificantBits()); + } + + private void writeString( final char[] s ) throws IOException { + writer.writeByte(QType.STRING.getTypeCode()); + writer.writeByte((byte) 0); // attributes + final byte[] encoded = String.valueOf(s).getBytes(encoding); + writer.writeInt(encoded.length); + writer.write(encoded); + } + + private void writeNullItem() { + writer.writeByte(QType.NULL_ITEM.getTypeCode()); + writer.writeByte((byte) 0); + } + + private void writeError( final Exception e ) throws IOException { + writer.writeByte(QType.ERROR.getTypeCode()); + writeSymbol(e.getMessage()); + } + + private void writeDictionary( final QDictionary d ) throws IOException, QException { + writer.writeByte(QType.DICTIONARY.getTypeCode()); + writeObject(d.getKeys()); + writeObject(d.getValues()); + } + + private void writeTable( final QTable t ) throws IOException, QException { + writer.writeByte(QType.TABLE.getTypeCode()); + writer.writeByte((byte) 0); // attributes + writer.writeByte(QType.DICTIONARY.getTypeCode()); + writeObject(t.getColumns()); + writeObject(t.getData()); + } + + private void writeKeyedTable( final QKeyedTable t ) throws IOException, QException { + writer.writeByte(QType.KEYED_TABLE.getTypeCode()); + writeObject(t.getKeys()); + writeObject(t.getValues()); + } + + private void writeLambda( final QLambda l ) throws IOException, QException { + if ( l.getParameters() == null || l.getParameters().length == 0 ) { + writer.writeByte(QType.LAMBDA.getTypeCode()); + writer.writeByte((byte) 0); + writeString(l.getExpression().toCharArray()); + } else { + writer.writeByte(QType.LAMBDA_PART.getTypeCode()); + writer.writeInt(l.getParameters().length + 1); + writer.writeByte(QType.LAMBDA.getTypeCode()); + writer.writeByte((byte) 0); + writeString(l.getExpression().toCharArray()); + for ( final Object p : l.getParameters() ) { + writeObject(p); + } + } + } + + static class ByteOutputStream extends OutputStream { + private static final int BUFFER_SIZE = 128; + + protected byte buffer[]; + + protected int count; + + ByteOutputStream() { + buffer = new byte[BUFFER_SIZE]; + } + + ByteOutputStream(final int bufferSize) { + buffer = new byte[bufferSize]; + } + + public void writeShort( final short value ) { + writeByte((byte) value); + writeByte((byte) (value >> 8)); + } + + public void writeInt( final int value ) { + writeShort((short) value); + writeShort((short) (value >> 16)); + } + + public void writeLong( final long value ) { + writeInt((int) value); + writeInt((int) (value >> 32)); + } + + public void writeLongBigEndian( final long value ) { + final byte[] arr = new byte[] { (byte) ((value >> 56) & 0xff), (byte) ((value >> 48) & 0xff), (byte) ((value >> 40) & 0xff), + (byte) ((value >> 32) & 0xff), (byte) ((value >> 24) & 0xff), (byte) ((value >> 16) & 0xff), + (byte) ((value >> 8) & 0xff), (byte) ((value >> 0) & 0xff) }; + for ( int i = 0; i < arr.length; i++ ) { + writeByte(arr[i]); + } + } + + public void writeFloat( final float value ) { + writeInt(Float.floatToIntBits(value)); + } + + public void writeDouble( final double value ) { + writeLong(Double.doubleToLongBits(value)); + } + + public void writeByte( final byte value ) { + final int newcount = count + 1; + if ( newcount > buffer.length ) { + final byte[] copy = new byte[count + BUFFER_SIZE]; + System.arraycopy(buffer, 0, copy, 0, buffer.length); + buffer = copy; + } + buffer[count] = value; + count = newcount; + } + + @Override + public void write( final int b ) { + final int newcount = count + 1; + if ( newcount > buffer.length ) { + final byte[] copy = new byte[count + BUFFER_SIZE]; + System.arraycopy(buffer, 0, copy, 0, buffer.length); + buffer = copy; + } + buffer[count] = (byte) b; + count = newcount; + } + + @Override + public void write( final byte b[], final int off, final int len ) { + if ( (off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) ) { + throw new IndexOutOfBoundsException("Attempt to write outside of the buffer. Offset: " + off + ", Size: " + len + ", Buffer: " + b.length + "."); + } else if ( len == 0 ) { + return; + } + final int newcount = count + len; + if ( newcount > buffer.length ) { + final byte[] copy = new byte[Math.max(count + BUFFER_SIZE, newcount)]; + System.arraycopy(buffer, 0, copy, 0, buffer.length); + buffer = copy; + } + System.arraycopy(b, off, buffer, count, len); + count = newcount; + } + + public int size() { + return count; + } + + @Override + public void close() throws IOException { + // + } + + /** + * Creates copy of internal buffer. + * + * @return copy of the internal buffer as byte[] + */ + public byte[] toByteArray() { + final byte[] copy = new byte[count]; + System.arraycopy(buffer, 0, copy, 0, Math.min(buffer.length, count)); + return copy; + } + + } +} diff --git a/src/main/java/com/exxeleron/qjava/QWriterException.java b/src/main/java/com/exxeleron/qjava/QWriterException.java new file mode 100644 index 0000000..93f3271 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/QWriterException.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +/** + * Exception representing q serialization error. + */ +public class QWriterException extends QException { + + private static final long serialVersionUID = -6996779408402779829L; + + /** + * Constructs a {@link QWriterException} with the specified detailed message and cause. + * + * @param message + * the detail message + * @param cause + * the cause + */ + public QWriterException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a {@link QWriterException} with the specified detailed message. + * + * @param message + * the detail message + */ + public QWriterException(final String message) { + super(message); + } + +} diff --git a/src/main/java/com/exxeleron/qjava/Utils.java b/src/main/java/com/exxeleron/qjava/Utils.java new file mode 100644 index 0000000..b6df058 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/Utils.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.TimeZone; + +/** + * Utility methods + */ +class Utils { + static final long DAY_MILLIS = 86400000L; + + static final long QEPOCH_MILLIS = 10957 * DAY_MILLIS; + + static final TimeZone TIME_ZONE = TimeZone.getDefault(); + + static long tzOffsetFromQ( final long dt ) { + return dt + TIME_ZONE.getOffset(dt); + } + + static long tzOffsetToQ( final long dt ) { + return dt - TIME_ZONE.getOffset(dt - TIME_ZONE.getOffset(dt)); + } + + private static final char[] HEXES = "0123456789ABCDEF".toCharArray(); + + static String getHex( final byte[] raw ) { + char[] hexChars = new char[raw.length * 2]; + for ( int j = 0; j < raw.length; j++ ) { + int v = raw[j] & 0xFF; + hexChars[j * 2] = HEXES[v >>> 4]; + hexChars[j * 2 + 1] = HEXES[v & 0x0F]; + } + return new String(hexChars); + } + + static String arrayToString( final Object list ) { + if ( list == null || !list.getClass().isArray() || Array.getLength(list) == 0 ) { + return "[]"; + } else { + final int length = Array.getLength(list); + final StringBuffer buffer = new StringBuffer("["); + + Object obj = Array.get(list, 0); + buffer.append(obj == null ? null : obj.getClass().isArray() ? arrayToString(obj) : obj); + for ( int i = 1; i < length; i++ ) { + obj = Array.get(list, i); + buffer.append(", "); + buffer.append(obj == null ? null : obj.getClass().isArray() ? arrayToString(obj) : obj); + } + buffer.append(']'); + return buffer.toString(); + } + } + + static boolean deepArraysEquals( final Object l, final Object r ) { + if ( l == null && r == null ) { + return true; + } + + if ( l == null || r == null ) { + return false; + } + + if ( l.getClass() != r.getClass() ) { + return false; + } + + final int length = Array.getLength(l); + if ( length != Array.getLength(r) ) { + return false; + } + + for ( int i = 0; i < length; i++ ) { + final Object lv = Array.get(l, i); + final Object rv = Array.get(r, i); + + if ( lv == rv || lv == null && rv == null ) { + continue; + } + + if ( lv == null || rv == null || lv.getClass() != rv.getClass() ) { + return false; + } + + if ( lv.getClass().isArray() ) { + if ( !deepArraysEquals(lv, rv) ) { + return false; + } + } else { + if ( !lv.equals(rv) ) { + return false; + } + } + } + + return true; + } + + static int arrayHashCode( final Object list ) { + if ( list instanceof Object[] ) { + return Arrays.hashCode((Object[]) list); + } else if ( list instanceof boolean[] ) { + return Arrays.hashCode((boolean[]) list); + } else if ( list instanceof byte[] ) { + return Arrays.hashCode((byte[]) list); + } else if ( list instanceof short[] ) { + return Arrays.hashCode((short[]) list); + } else if ( list instanceof int[] ) { + return Arrays.hashCode((int[]) list); + } else if ( list instanceof long[] ) { + return Arrays.hashCode((long[]) list); + } else if ( list instanceof float[] ) { + return Arrays.hashCode((float[]) list); + } else if ( list instanceof double[] ) { + return Arrays.hashCode((double[]) list); + } else { + throw new IllegalArgumentException("Argument is not an array"); + } + } + +} diff --git a/src/sample/java/AsynchQuery.java b/src/sample/java/AsynchQuery.java new file mode 100644 index 0000000..5a428ae --- /dev/null +++ b/src/sample/java/AsynchQuery.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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. + */ +import java.io.IOException; +import java.util.Random; + +import com.exxeleron.qjava.QCallbackConnection; +import com.exxeleron.qjava.QErrorMessage; +import com.exxeleron.qjava.QMessage; +import com.exxeleron.qjava.QMessagesListener; + +public class AsynchQuery { + + public static void main( final String[] args ) throws IOException { + final QCallbackConnection q = new QCallbackConnection(args.length >= 1 ? args[0] : "localhost", args.length >= 2 ? Integer.parseInt(args[1]) : 5001, "", ""); + + final QMessagesListener listener = new QMessagesListener() { + + // definition of messageListener that prints every message it gets on + // stdout + public void messageReceived( final QMessage message ) { + System.out.println("Asynch result:" + (message.getData().toString())); + } + + public void errorReceived( final QErrorMessage message ) { + System.err.println("Asynch error:" + (message.getCause().toString())); + } + }; + + q.addMessagesListener(listener); + try { + q.open(); // open connection + + // definition of asynchronous multiply function + // queryid - unique identifier of function call - used to identify + // the result + // a, b - actual parameters to the query + q.sync("asynchMult:{[queryid;a;b] res:a*b; (neg .z.w)(`queryid`result!(queryid;res)) }"); + + q.startListener(); // activate messageListener + + final Random gen = new Random(); + // send asynchronous queries + for ( int i = 0; i < 10; i++ ) { + final int a = gen.nextInt(), b = gen.nextInt(); + System.out.println("Asynch call with queryid=" + i + " with arguments" + a + "," + b); + q.async("asynchMult", i, a, b); + } + + Thread.sleep(2000); + q.stopListener(); + } catch ( final Exception e ) { + System.err.println(e); + } finally { + q.close(); + } + } +} diff --git a/src/sample/java/Console.java b/src/sample/java/Console.java new file mode 100644 index 0000000..ee5a297 --- /dev/null +++ b/src/sample/java/Console.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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. + */ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import com.exxeleron.qjava.QBasicConnection; +import com.exxeleron.qjava.QConnection; +import com.exxeleron.qjava.QException; + +public class Console { + + public static void main( final String[] args ) throws IOException { + final QConnection q = new QBasicConnection(args.length >= 1 ? args[0] : "localhost", args.length >= 2 ? Integer.parseInt(args[1]) : 5001, "user", + "pwd"); + final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + + try { + q.open(); + System.out.printf("conn: %1$s protocol version: %2$d\n", q, q.getProtocolVersion()); + + while ( true ) { + System.out.print("Q)"); + final String line = bufferedReader.readLine(); + + if ( line.equals("\\\\") ) { + break; + } else { + try { + System.out.println(": " + Utils.resultToString(q.sync(line))); + } catch ( final QException e ) { + System.err.println("`" + e.getMessage()); + } + } + } + } catch ( final QException e ) { + System.err.println(e); + } finally { + q.close(); + } + } +} diff --git a/src/sample/java/Publisher.java b/src/sample/java/Publisher.java new file mode 100644 index 0000000..7d1e871 --- /dev/null +++ b/src/sample/java/Publisher.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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. + */ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Date; +import java.util.Random; + +import com.exxeleron.qjava.QBasicConnection; +import com.exxeleron.qjava.QConnection; +import com.exxeleron.qjava.QException; +import com.exxeleron.qjava.QTime; + +public class Publisher { + + public static void main( final String[] args ) throws IOException { + final QConnection q = new QBasicConnection(args.length >= 1 ? args[0] : "localhost", args.length >= 2 ? Integer.parseInt(args[1]) : 5001, "", ""); + final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + + try { + q.open(); + System.out.printf("conn: %1$s protocol version: %2$d\n", q, q.getProtocolVersion()); + System.out.println("Press to close application"); + + final PublisherTask pt = new PublisherTask(q); + final Thread t = new Thread(pt, "publisher-thread"); + t.start(); + + bufferedReader.readLine(); + pt.stop(); + t.join(); + } catch ( final Exception e ) { + System.err.println(e); + } finally { + q.close(); + } + } + +} + +class PublisherTask implements Runnable { + private final QConnection q; + boolean running = true; + private final Random r; + + public PublisherTask(final QConnection q) { + this.q = q; + this.r = new Random(System.currentTimeMillis()); + } + + public void stop() { + running = false; + } + + public void run() { + while ( running ) { + try { + // publish data to tick + // function: .u.upd + // table: ask + q.sync(".u.upd", "ask", getAskData()); + } catch ( final QException e1 ) { + // q error + e1.printStackTrace(); + } catch ( final IOException e1 ) { + // problem with connection + running = false; + } + + try { + Thread.sleep(1000); + } catch ( final InterruptedException e ) { + e.printStackTrace(); + } + } + } + + private Object[] getAskData() { + final int c = r.nextInt(10); + final Object[] data = new Object[] { new QTime[c], new String[c], new String[c], new float[c] }; + + for ( int i = 0; i < c; i++ ) { + ((QTime[]) data[0])[i] = new QTime(new Date()); + ((String[]) data[1])[i] = "INSTR_" + r.nextInt(100); + ((String[]) data[2])[i] = "qJava"; + ((float[]) data[3])[i] = r.nextFloat() * r.nextInt(100); + } + + return data; + } +} diff --git a/src/sample/java/Subscriber.java b/src/sample/java/Subscriber.java new file mode 100644 index 0000000..4a8fffa --- /dev/null +++ b/src/sample/java/Subscriber.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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. + */ +import java.io.IOException; + +import com.exxeleron.qjava.QCallbackConnection; +import com.exxeleron.qjava.QErrorMessage; +import com.exxeleron.qjava.QMessage; +import com.exxeleron.qjava.QMessagesListener; + +public class Subscriber { + + public static void main( final String[] args ) throws IOException { + final QCallbackConnection q = new QCallbackConnection(args.length >= 1 ? args[0] : "localhost", args.length >= 2 ? Integer.parseInt(args[1]) : 5001, "", ""); + + final QMessagesListener listener = new QMessagesListener() { + + // definition of messageListener that prints every message it gets on stdout + public void messageReceived( final QMessage message ) { + System.out.println((message.getData())); + } + + public void errorReceived( final QErrorMessage message ) { + System.err.println((message.getCause())); + } + }; + + q.addMessagesListener(listener); + try { + q.open(); // open connection + System.out.println("Press to close application"); + + q.sync("sub:{[x] .sub.h: .z.w }"); // subscription definition + q.sync(".z.ts:{ (neg .sub.h) .z.p}"); // data generation definition + q.sync("system \"t 500\""); // start data generation + q.startListener(); // activate messageListener + q.async("sub", 0); // subscription + + System.in.read(); + q.stopListener(); + q.sync("system \"t 0\""); // stop data generation + } catch ( final Exception e ) { + System.err.println(e); + } finally { + q.close(); + } + } +} diff --git a/src/sample/java/SyncQuery.java b/src/sample/java/SyncQuery.java new file mode 100644 index 0000000..c87b8a3 --- /dev/null +++ b/src/sample/java/SyncQuery.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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. + */ +import java.io.IOException; +import java.util.Arrays; + +import com.exxeleron.qjava.QBasicConnection; +import com.exxeleron.qjava.QConnection; +import com.exxeleron.qjava.QException; + +public class SyncQuery { + + public static void main( final String[] args ) throws IOException { + // create connection to localhost:5001 with login myUser and password myPassword + final QConnection q = new QBasicConnection("localhost", 5001, "myUser", "myPassword"); + try { + q.open(); // open connection + // sending query {til x}[10] and storing its result in list + final int[] list = (int[]) q.sync("{`int$ til x}", 10); + // print result + System.out.println(Arrays.toString(list)); + } catch ( final QException e ) { + System.err.println(e); + } finally { + q.close(); // close connection + } + } + +} diff --git a/src/sample/java/TickClient.java b/src/sample/java/TickClient.java new file mode 100644 index 0000000..e2da11d --- /dev/null +++ b/src/sample/java/TickClient.java @@ -0,0 +1,188 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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. + */ +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.table.DefaultTableModel; + +import com.exxeleron.qjava.QCallbackConnection; +import com.exxeleron.qjava.QErrorMessage; +import com.exxeleron.qjava.QMessage; +import com.exxeleron.qjava.QMessagesListener; +import com.exxeleron.qjava.QTable; + +public class TickClient { + + static QCallbackConnection q; + + public static void main( final String[] args ) { + final TickClientFrame f = new TickClientFrame(); + f.setTitle("TickClient demo application"); + f.setSize(700, 500); + + f.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing( final WindowEvent e ) { + if ( q != null ) { + try { + q.stopListener(); + q.close(); + } catch ( final IOException e1 ) { + e1.printStackTrace(); + } + } + System.exit(0); + } + }); + + f.setVisible(true); + } + + static class Printout implements QMessagesListener { + + public void messageReceived( final QMessage message ) { + System.out.println(Utils.resultToString(message.getData())); + } + + public void errorReceived( final QErrorMessage message ) { + System.err.println(Utils.resultToString(message.getCause())); + } + } + + static class TableFeed implements QMessagesListener { + + private final DefaultTableModel model; + + public TableFeed(final DefaultTableModel model) { + this.model = model; + } + + public void messageReceived( final QMessage message ) { + final Object data = message.getData(); + if ( data instanceof Object[] ) { + final Object[] list = ((Object[]) data); + if ( list.length == 3 && list[0].equals("upd") && list[2] instanceof QTable ) { + final QTable table = (QTable) list[2]; + for ( final QTable.Row row : table ) { + model.insertRow(0, row.toArray()); + } + } + } + } + + public void errorReceived( final QErrorMessage message ) { + // ignore + } + } + + static class StartSubscriptionAction implements ActionListener { + + private final TickClientFrame tickClient; + + public StartSubscriptionAction(final TickClientFrame tickClientFrame) { + this.tickClient = tickClientFrame; + } + + public void actionPerformed( final ActionEvent e ) { + if ( q == null ) { + final String[] conn = tickClient.qhostTF.getText().split(":"); + q = new QCallbackConnection(conn.length >= 1 ? conn[0] : "localhost", conn.length >= 2 ? Integer.parseInt(conn[1]) : 5010, null, null); + + try { + q.open(); + final QTable model = (QTable) ((Object[]) q.sync(".u.sub", tickClient.qtableTF.getText(), ""))[1]; + tickClient.table.setModel(new DefaultTableModel(model.getColumns(), 0)); + + q.addMessagesListener(new TableFeed((DefaultTableModel) tickClient.table.getModel())); + } catch ( final Exception e1 ) { + e1.printStackTrace(); + if ( q != null ) { + try { + q.close(); + q = null; + } catch ( final IOException e2 ) { + e1.printStackTrace(); + } + } + } + } + q.startListener(); + } + } + + static class StopSubscriptionAction implements ActionListener { + + public void actionPerformed( final ActionEvent arg0 ) { + if ( q != null ) { + q.stopListener(); + } + } + } + + static class TickClientFrame extends JFrame { + private static final long serialVersionUID = 7271896087017080273L; + + JTextField qhostTF; + JTextField qtableTF; + JTable table; + + public TickClientFrame() { + initComponents(); + } + + private void initComponents() { + final JPanel toolboxPanel = new JPanel(); + toolboxPanel.setLayout(new FlowLayout()); + + toolboxPanel.add(new JLabel("kdb+ host:")); + qhostTF = new JTextField(15); + qhostTF.setText("localhost:5010"); + toolboxPanel.add(qhostTF); + + toolboxPanel.add(new JLabel("kdb+ table:")); + qtableTF = new JTextField(15); + qtableTF.setText("trade"); + toolboxPanel.add(qtableTF); + + final JButton subscribeBtn = new JButton("Start"); + toolboxPanel.add(subscribeBtn); + subscribeBtn.addActionListener(new StartSubscriptionAction(this)); + + final JButton unsubscribeBtn = new JButton("Pause"); + toolboxPanel.add(unsubscribeBtn); + unsubscribeBtn.addActionListener(new StopSubscriptionAction()); + + table = new JTable(0, 0); + final JScrollPane dataPanel = new JScrollPane(table); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(toolboxPanel, BorderLayout.NORTH); + getContentPane().add(dataPanel, BorderLayout.CENTER); + } + } +} diff --git a/src/sample/java/TickSubscriber.java b/src/sample/java/TickSubscriber.java new file mode 100644 index 0000000..371c0d1 --- /dev/null +++ b/src/sample/java/TickSubscriber.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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. + */ +import java.io.IOException; +import java.util.Arrays; + +import com.exxeleron.qjava.QCallbackConnection; +import com.exxeleron.qjava.QErrorMessage; +import com.exxeleron.qjava.QMessage; +import com.exxeleron.qjava.QMessagesListener; +import com.exxeleron.qjava.QTable; + +public class TickSubscriber { + + public static void main( final String[] args ) throws IOException { + final QCallbackConnection q = new QCallbackConnection("localhost", 9010, "", ""); + final QMessagesListener listener = new QMessagesListener() { + + public void messageReceived( final QMessage message ) { + final Object data = message.getData(); + if ( data instanceof Object[] ) { + // unpack upd message + final Object[] params = ((Object[]) data); + if ( params.length == 3 && params[0].equals("upd") && params[2] instanceof QTable ) { + final QTable table = (QTable) params[2]; + for ( final QTable.Row row : table ) { + System.out.println(Arrays.toString(row.toArray())); + } + } + } + } + + public void errorReceived( final QErrorMessage message ) { + System.err.println(Utils.resultToString(message.getCause())); + } + }; + + q.addMessagesListener(listener); + try { + q.open(); + System.out.println("Press to close application"); + + final Object response = q.sync(".u.sub", "trade", ""); // subscribe to tick + final QTable model = (QTable) ((Object[]) response)[1]; // get table model + + q.startListener(); // activate messageListener + + System.in.read(); + q.stopListener(); + } catch ( final Exception e ) { + System.err.println(e); + } finally { + q.close(); + } + } +} diff --git a/src/sample/java/Utils.java b/src/sample/java/Utils.java new file mode 100644 index 0000000..521d3f9 --- /dev/null +++ b/src/sample/java/Utils.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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. + */ +import java.lang.reflect.Array; + +import com.exxeleron.qjava.QDictionary; +import com.exxeleron.qjava.QKeyedTable; +import com.exxeleron.qjava.QTable; +import com.exxeleron.qjava.QDictionary.KeyValuePair; +import com.exxeleron.qjava.QTable.Row; + +public final class Utils { + + static String arrayToString( final Object list ) { + if ( list == null || !list.getClass().isArray() || Array.getLength(list) == 0 ) { + return "[]"; + } else { + final int length = Array.getLength(list); + final StringBuffer buffer = new StringBuffer("["); + + Object obj = Array.get(list, 0); + buffer.append(obj == null ? null : obj.getClass().isArray() ? arrayToString(obj) : obj); + for ( int i = 1; i < length; i++ ) { + obj = Array.get(list, i); + buffer.append(", "); + buffer.append(obj == null ? null : obj.getClass().isArray() ? arrayToString(obj) : obj); + } + buffer.append(']'); + return buffer.toString(); + } + } + + static String resultToString( final Object obj ) { + if ( obj == null ) { + return "null"; + } else if ( obj.getClass().isArray() ) { + return Utils.arrayToString(obj); + } else if ( obj instanceof QDictionary ) { + final StringBuffer buffer = new StringBuffer(); + final QDictionary d = (QDictionary) obj; + for ( final KeyValuePair e : d ) { + buffer.append(resultToString(e.getKey())); + buffer.append(" | "); + buffer.append(resultToString(e.getValue())); + buffer.append('\n'); + } + return buffer.toString(); + } else if ( obj instanceof QTable ) { + final QTable t = (QTable) obj; + final StringBuffer buffer = new StringBuffer(); + for ( final Row row : t ) { + for ( final Object object : row ) { + buffer.append(object); + buffer.append('\t'); + } + buffer.append('\n'); + } + + return buffer.toString(); + } else if ( obj instanceof QKeyedTable ) { + final QKeyedTable kt = (QKeyedTable) obj; + final StringBuffer buffer = new StringBuffer(); + for ( final QKeyedTable.KeyValuePair kv : kt ) { + for ( final Object object : kv.getKey() ) { + buffer.append(object); + buffer.append('\t'); + } + buffer.append(" | "); + for ( final Object object : kv.getValue() ) { + buffer.append(object); + buffer.append('\t'); + } + buffer.append('\n'); + } + + return buffer.toString(); + } else { + return obj.toString(); + } + } +} diff --git a/src/test/java/com/exxeleron/qjava/QExpressions.java b/src/test/java/com/exxeleron/qjava/QExpressions.java new file mode 100644 index 0000000..d906564 --- /dev/null +++ b/src/test/java/com/exxeleron/qjava/QExpressions.java @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * Utility class for testing serialization and deserialization. + */ +class QExpressions { + + private final Map reference = new LinkedHashMap(); + private final Map referenceSerializationAlt = new LinkedHashMap(); + + private void initExpressions() throws QException { + reference.put("1+`", new QException("type")); + reference.put("()", new Object[0]); + reference.put("::", null); + reference.put("1", 1); + reference.put("1i", 1); + reference.put("-234h", (short) -234); + reference.put("1b", true); + reference.put("0x2a", (byte) 0x2a); + reference.put("89421099511627575j", 89421099511627575L); + reference.put("3.234", 3.234); + reference.put("5.5e", (float) 5.5); + reference.put("\"0\"", '0'); + reference.put("\"abc\"", "abc".toCharArray()); + reference.put("\"\"", "".toCharArray()); + reference.put("\"quick brown fox jumps over a lazy dog\"", "quick brown fox jumps over a lazy dog".toCharArray()); + reference.put("`abc", "abc"); + reference.put("`quickbrownfoxjumpsoveralazydog", "quickbrownfoxjumpsoveralazydog"); + reference.put("2000.01.04D05:36:57.600", new QTimestamp(279417600000000L)); + reference.put("2001.01m", new QMonth(12)); + reference.put("2001.01.01", new QDate(366)); + reference.put("2000.05.01", new QDate(121)); + reference.put("2000.01.04T05:36:57.600", new QDateTime(3.234)); + reference.put("0D05:36:57.600", new QTimespan(20217600000000L)); + reference.put("12:01", new QMinute(721)); + reference.put("12:05:00", new QSecond(43500)); + reference.put("12:04:59.123", new QTime(43499123)); + reference.put("0b", QType.getQNull(QType.BOOL)); + reference.put("0x00", QType.getQNull(QType.BYTE)); + reference.put("0Nh", QType.getQNull(QType.SHORT)); + reference.put("0N", QType.getQNull(QType.INT)); + reference.put("0Ni", QType.getQNull(QType.INT)); + reference.put("0Nj", QType.getQNull(QType.LONG)); + reference.put("0Ne", QType.getQNull(QType.FLOAT)); + reference.put("0n", QType.getQNull(QType.DOUBLE)); + reference.put("\" \"", QType.getQNull(QType.CHAR)); + reference.put("`", QType.getQNull(QType.SYMBOL)); + reference.put("0Np", QType.getQNull(QType.TIMESTAMP)); + reference.put("0Nm", QType.getQNull(QType.MONTH)); + reference.put("0Nd", QType.getQNull(QType.DATE)); + reference.put("0Nz", QType.getQNull(QType.DATETIME)); + reference.put("0Nn", QType.getQNull(QType.TIMESPAN)); + reference.put("0Nu", QType.getQNull(QType.MINUTE)); + reference.put("0Nv", QType.getQNull(QType.SECOND)); + reference.put("0Nt", QType.getQNull(QType.TIME)); + reference.put("(0b;1b;0b)", new boolean[] { false, true, false }); + referenceSerializationAlt.put("(0b;1b;0b)", new Boolean[] { false, true, false }); + reference.put("(0x01;0x02;0xff)", new byte[] { 1, 2, (byte) 255 }); + referenceSerializationAlt.put("(0x01;0x02;0xff)", new Byte[] { 1, 2, (byte) 255 }); + reference.put("(1h;2h;3h)", new short[] { 1, 2, 3 }); + referenceSerializationAlt.put("(1h;2h;3h)", new Short[] { 1, 2, 3 }); + reference.put("1 2 3", new int[] { 1, 2, 3 }); + referenceSerializationAlt.put("1 2 3", new Integer[] { 1, 2, 3 }); + reference.put("(1i;2i;3i)", new int[] { 1, 2, 3 }); + referenceSerializationAlt.put("(1i;2i;3i)", new Integer[] { 1, 2, 3 }); + reference.put("(1j;2j;3j)", new long[] { 1, 2, 3 }); + referenceSerializationAlt.put("(1j;2j;3j)", new Long[] { 1L, 2L, 3L }); + reference.put("(5.5e; 8.5e)", new float[] { 5.5f, 8.5f }); + referenceSerializationAlt.put("(5.5e; 8.5e)", new Float[] { 5.5f, 8.5f }); + reference.put("3.23 6.46", new double[] { 3.23, 6.46 }); + referenceSerializationAlt.put("3.23 6.46", new Double[] { 3.23, 6.46 }); + reference.put("(1;`bcd;\"0bc\";5.5e)", new Object[] { 1, "bcd", "0bc".toCharArray(), (float) 5.5 }); + reference.put("(enlist 1h; 2; enlist 3j)", new Object[] { new short[] { 1 }, 2, new long[] { 3 } }); + reference.put("`the`quick`brown`fox", new String[] { "the", "quick", "brown", "fox" }); + reference.put("2000.01.04D05:36:57.600 0Np", new QTimestamp[] { new QTimestamp(279417600000000L), new QTimestamp(Long.MIN_VALUE) }); + reference.put("(2001.01m; 0Nm)", new QMonth[] { new QMonth(12), new QMonth(Integer.MIN_VALUE) }); + reference.put("2001.01.01 2000.05.01 0Nd", new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) }); + reference.put("2000.01.04T05:36:57.600 0Nz", new QDateTime[] { new QDateTime(3.234), new QDateTime(Double.NaN) }); + reference.put("0D05:36:57.600 0Nn", new QTimespan[] { new QTimespan(20217600000000L), new QTimespan(Long.MIN_VALUE) }); + reference.put("12:01 0Nu", new QMinute[] { new QMinute(721), new QMinute(Integer.MIN_VALUE) }); + reference.put("12:05:00 0Nv", new QSecond[] { new QSecond(43500), new QSecond(Integer.MIN_VALUE) }); + reference.put("12:04:59.123 0Nt", new QTime[] { new QTime(43499123), new QTime(Integer.MIN_VALUE) }); + reference.put("(enlist `a)!(enlist 1)", new QDictionary(new String[] { "a" }, new int[] { 1 })); + reference.put("1 2!`abc`cdefgh", new QDictionary(new int[] { 1, 2 }, new String[] { "abc", "cdefgh" })); + reference.put("(1;2h;3.3;\"4\")!(`one;2 3;\"456\";(7;8 9))", new QDictionary(new Object[] { 1, (short) 2, 3.3, '4' }, + new Object[] { "one", new int[] { 2, 3 }, "456".toCharArray(), new Object[] { 7, new int[] { 8, 9 } } })); + reference.put("(0 1; 2 3)!`first`second", new QDictionary(new Object[] { new int[] { 0, 1 }, new int[] { 2, 3 } }, new String[] { "first", "second" })); + reference.put("`A`B`C!((1;2.2;3);(`x`y!(`a;2));5.5)", new QDictionary(new String[] { "A", "B", "C" }, new Object[] { + new Object[] { 1, 2.2, 3 }, + new QDictionary( + new String[] { "x", "y" }, + new Object[] { "a", 2 }), + 5.5 })); + reference.put("flip `abc`def!(1 2 3; 4 5 6)", new QTable(new String[] { "abc", "def" }, new Object[] { new int[] { 1, 2, 3 }, new int[] { 4, 5, 6 } })); + reference.put("flip `name`iq!(`Dent`Beeblebrox`Prefect;98 42 126)", + new QTable(new String[] { "name", "iq" }, new Object[] { new String[] { "Dent", "Beeblebrox", "Prefect" }, new int[] { 98, 42, 126 } })); + reference.put("([] sc:1 2 3; nsc:(1 2; 3 4; 5 6 7))", new QTable(new String[] { "sc", "nsc" }, new Object[] { + new int[] { 1, 2, 3 }, + new Object[] { new int[] { 1, 2 }, + new int[] { 3, 4 }, + new int[] { 5, 6, 7 } } })); + reference.put("([] name:`symbol$(); iq:`int$())", new QTable(new String[] { "name", "iq" }, new Object[] { new String[] {}, new int[] {} })); + reference.put("([] pos:`d1`d2`d3;dates:(2001.01.01;2000.05.01;0Nd))", new QTable(new String[] { "pos", "dates" }, + new Object[] { new String[] { "d1", "d2", "d3" }, new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) } })); + reference.put("([eid:1001 1002 1003] pos:`d1`d2`d3;dates:(2001.01.01;2000.05.01;0Nd))", new QKeyedTable(new QTable(new String[] { "eid" }, + new Object[] { new int[] { 1001, 1002, 1003 } }), new QTable(new String[] { "pos", "dates" }, + new Object[] { new String[] { "d1", "d2", "d3" }, new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) } }))); + reference.put("{x+y}", new QLambda("{x+y}")); + reference.put("{x+y}[3]", new QLambda("{x+y}", new Object[] { 3 })); + reference.put("0Ng", new UUID(0, 0)); + reference.put("\"G\"$\"0162fca8-c01f-7450-208c-55a06b2f2c54\"", new UUID(99919943463564368L, 2345343653434567764L)); + reference.put("\"G\"$\"00000000-0000-0000-0000-000000000000\"", new UUID(0, 0)); + reference.put("(\"G\"$\"0162fca8-c01f-7450-208c-55a06b2f2c54\"; 0Ng)", + new UUID[] { new UUID(99919943463564368L, 2345343653434567764L), new UUID(0, 0) }); + + } + + private final Map expressions = new LinkedHashMap(); + + QExpressions(final String file) { + try { + initExpressions(); + } catch ( final QException e ) { + e.printStackTrace(); + } + try { + final BufferedReader input = new BufferedReader(new FileReader(file)); + try { + String query = null; + String expression = null; + + while ( true ) { + query = input.readLine(); + expression = input.readLine(); + + if ( query != null && expression != null && !expression.equals("") ) { + expressions.put(query, stringToByteArray(expression)); + } else { + break; + } + } + } finally { + input.close(); + } + } catch ( final IOException ex ) { + ex.printStackTrace(); + } + } + + Set getExpressions() { + return expressions.keySet(); + } + + Object getReferenceObject( final String expression ) { + return reference.get(expression); + } + + boolean hasReferenceObjectAlt( final String expression ) { + return referenceSerializationAlt.containsKey(expression); + } + + Object getReferenceObjectAlt( final String expression ) { + return referenceSerializationAlt.get(expression); + } + + public byte[] getBinaryExpression( final String expression ) { + return expressions.get(expression); + } + + private static byte[] stringToByteArray( final String hex ) { + final int numberChars = hex.length(); + final byte[] bytes = new byte[numberChars / 2]; + for ( int i = 0; i < numberChars; i += 2 ) { + bytes[i / 2] = (byte) (short) Short.valueOf(hex.substring(i, i + 2), 16); + } + return bytes; + } + +} diff --git a/src/test/java/com/exxeleron/qjava/TestDateTime.java b/src/test/java/com/exxeleron/qjava/TestDateTime.java new file mode 100644 index 0000000..c65d4a7 --- /dev/null +++ b/src/test/java/com/exxeleron/qjava/TestDateTime.java @@ -0,0 +1,333 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import static org.junit.Assert.assertEquals; + +import java.util.Calendar; +import java.util.Date; + +import org.junit.Test; + +public class TestDateTime { + + @Test + public void testQDate() { + final Calendar c = Calendar.getInstance(); + c.set(1995, 6, 1); + assertEquals(-1645, (Object) new QDate(c.getTime()).getValue()); + c.set(1999, 0, 1); + assertEquals(-365, (Object) new QDate(c.getTime()).getValue()); + c.set(2000, 0, 1); + assertEquals(0, (Object) new QDate(c.getTime()).getValue()); + c.set(2005, 6, 1); + assertEquals(2008, (Object) new QDate(c.getTime()).getValue()); + c.set(2010, 0, 1); + assertEquals(3653, (Object) new QDate(c.getTime()).getValue()); + + assertEquals(Integer.MIN_VALUE, (Object) new QDate((Date) null).getValue()); + } + + @Test + public void testQDateToString() { + assertEquals("1995.07.01", new QDate(-1645).toString()); + assertEquals("1999.01.01", new QDate(-365).toString()); + assertEquals("2000.01.01", new QDate(0).toString()); + assertEquals("2005.07.01", new QDate(2008).toString()); + assertEquals("2010.01.01", new QDate(3653).toString()); + + assertEquals("0Nd", new QDate(Integer.MIN_VALUE).toString()); + } + + @Test + public void testQDateFromString() { + assertEquals(new QDate(-1645), QDate.fromString("1995.07.01")); + assertEquals(new QDate(-365), QDate.fromString("1999.01.01")); + assertEquals(new QDate(0), QDate.fromString("2000.01.01")); + assertEquals(new QDate(2008), QDate.fromString("2005.07.01")); + assertEquals(new QDate(3653), QDate.fromString("2010.01.01")); + + assertEquals(new QDate(Integer.MIN_VALUE), QDate.fromString(null)); + assertEquals(new QDate(Integer.MIN_VALUE), QDate.fromString("")); + assertEquals(new QDate(Integer.MIN_VALUE), QDate.fromString("0Nd")); + } + + @Test + public void testQDateTime() { + final Calendar c = Calendar.getInstance(); + c.set(1999, 0, 1, 23, 59, 59); + c.set(Calendar.MILLISECOND, 0); + assertEquals(-364.0000116, new QDateTime(c.getTime()).getValue(), 0.1); + c.set(2000, 0, 1, 0, 0, 0); + assertEquals(0., new QDateTime(c.getTime()).getValue(), 0.1); + c.set(2005, 6, 1, 1, 59, 59); + assertEquals(2008.0833218, new QDateTime(c.getTime()).getValue(), 0.1); + c.set(2010, 0, 1, 14, 23, 42); + assertEquals(3653.599792, new QDateTime(c.getTime()).getValue(), 0.1); + + assertEquals(Double.NaN, (Object) new QDateTime((Date) null).getValue()); + } + + @Test + public void testQDateTimeToString() { + assertEquals(true, new QDateTime(-364.0000115).toString().startsWith("1999.01.01T23:59:59.0")); + assertEquals("2000.01.01T00:00:00.000", new QDateTime(0.).toString()); + assertEquals(true, new QDateTime(2008.0833218).toString().startsWith("2005.07.01T01:59:59.0")); + assertEquals(true, new QDateTime(3653.599792).toString().startsWith("2010.01.01T14:23:42.0")); + + assertEquals("0Nz", new QDateTime(Double.NaN).toString()); + } + + @Test + public void testQDateTimeFromString() { + assertEquals(new QDateTime(-364.0000115).getValue(), QDateTime.fromString("1999.01.01T23:59:59.000").getValue(), 0.001); + assertEquals(new QDateTime(0.).getValue(), QDateTime.fromString("2000.01.01T00:00:00.000").getValue(), 0.001); + assertEquals(new QDateTime(2008.0833218).getValue(), QDateTime.fromString("2005.07.01T01:59:59.000").getValue(), 0.001); + assertEquals(new QDateTime(3653.599792).getValue(), QDateTime.fromString("2010.01.01T14:23:42.000").getValue(), 0.001); + + assertEquals(new QDateTime(Double.NaN), QDateTime.fromString(null)); + assertEquals(new QDateTime(Double.NaN), QDateTime.fromString("")); + assertEquals(new QDateTime(Double.NaN), QDateTime.fromString("0Nz")); + } + + @Test + public void testQMinute() { + final Calendar c = Calendar.getInstance(); + c.set(2000, 0, 1, 0, 0); + assertEquals(0, (Object) new QMinute(c.getTime()).getValue()); + c.set(2000, 0, 1, 13, 30); + assertEquals(810, (Object) new QMinute(c.getTime()).getValue()); + c.set(2000, 0, 1, 23, 59); + assertEquals(1439, (Object) new QMinute(c.getTime()).getValue()); + + assertEquals(Integer.MIN_VALUE, (Object) new QMinute((Date) null).getValue()); + } + + @Test + public void testQMinuteToString() { + assertEquals("00:00", new QMinute(0).toString()); + assertEquals("13:30", new QMinute(810).toString()); + assertEquals("23:59", new QMinute(1439).toString()); + + assertEquals("0Nu", new QMinute(Integer.MIN_VALUE).toString()); + } + + @Test + public void testQMinuteFromString() { + assertEquals(new QMinute(0), QMinute.fromString("00:00")); + assertEquals(new QMinute(810), QMinute.fromString("13:30")); + assertEquals(new QMinute(1439), QMinute.fromString("23:59")); + + assertEquals(new QMinute(Integer.MIN_VALUE), QMinute.fromString(null)); + assertEquals(new QMinute(Integer.MIN_VALUE), QMinute.fromString("")); + assertEquals(new QMinute(Integer.MIN_VALUE), QMinute.fromString("0Nu")); + } + + @Test + public void testQMonth() { + final Calendar c = Calendar.getInstance(); + c.set(1995, 0, 1); + assertEquals(-60, (Object) new QMonth(c.getTime()).getValue()); + c.set(1995, 6, 1); + assertEquals(-54, (Object) new QMonth(c.getTime()).getValue()); + c.set(2000, 0, 1); + assertEquals(0, (Object) new QMonth(c.getTime()).getValue()); + c.set(2005, 6, 1); + assertEquals(66, (Object) new QMonth(c.getTime()).getValue()); + c.set(2010, 0, 1); + assertEquals(120, (Object) new QMonth(c.getTime()).getValue()); + + assertEquals(Integer.MIN_VALUE, (Object) new QMonth((Date) null).getValue()); + } + + @Test + public void testQMonthToString() { + assertEquals("1995.01m", new QMonth(-60).toString()); + assertEquals("1995.07m", new QMonth(-54).toString()); + assertEquals("2000.01m", new QMonth(0).toString()); + assertEquals("2005.07m", new QMonth(66).toString()); + assertEquals("2010.01m", new QMonth(120).toString()); + + assertEquals("0Nm", new QMonth(Integer.MIN_VALUE).toString()); + } + + @Test + public void testQMonthFromString() { + assertEquals(new QMonth(-60), QMonth.fromString("1995.01m")); + assertEquals(new QMonth(-54), QMonth.fromString("1995.07m")); + assertEquals(new QMonth(0), QMonth.fromString("2000.01m")); + assertEquals(new QMonth(66), QMonth.fromString("2005.07m")); + assertEquals(new QMonth(120), QMonth.fromString("2010.01m")); + + assertEquals(new QMonth(Integer.MIN_VALUE), QMonth.fromString(null)); + assertEquals(new QMonth(Integer.MIN_VALUE), QMonth.fromString("")); + assertEquals(new QMonth(Integer.MIN_VALUE), QMonth.fromString("0Nm")); + } + + @Test + public void testQSecond() { + final Calendar c = Calendar.getInstance(); + c.set(2000, 0, 1, 0, 0, 0); + assertEquals(0, (Object) new QSecond(c.getTime()).getValue()); + c.set(2000, 0, 1, 13, 30, 13); + assertEquals(48613, (Object) new QSecond(c.getTime()).getValue()); + c.set(2000, 0, 1, 23, 59, 59); + assertEquals(86399, (Object) new QSecond(c.getTime()).getValue()); + + assertEquals(Integer.MIN_VALUE, (Object) new QSecond((Date) null).getValue()); + } + + @Test + public void testQSecondToString() { + assertEquals("00:00:00", new QSecond(0).toString()); + assertEquals("13:30:13", new QSecond(48613).toString()); + assertEquals("23:59:59", new QSecond(86399).toString()); + + assertEquals("0Nv", new QSecond(Integer.MIN_VALUE).toString()); + } + + @Test + public void testQSecondFromString() { + assertEquals(new QSecond(0), QSecond.fromString("00:00:00")); + assertEquals(new QSecond(48613), QSecond.fromString("13:30:13")); + assertEquals(new QSecond(86399), QSecond.fromString("23:59:59")); + + assertEquals(new QSecond(Integer.MIN_VALUE), QSecond.fromString(null)); + assertEquals(new QSecond(Integer.MIN_VALUE), QSecond.fromString("")); + assertEquals(new QSecond(Integer.MIN_VALUE), QSecond.fromString("0Nv")); + } + + @Test + public void testQTime() { + final Calendar c = Calendar.getInstance(); + c.set(2000, 0, 1, 0, 0, 0); + c.set(Calendar.MILLISECOND, 0); + assertEquals(0, (Object) new QTime(c.getTime()).getValue()); + c.set(2000, 0, 1, 13, 30, 13); + assertEquals(48613000, (Object) new QTime(c.getTime()).getValue()); + c.set(2000, 0, 1, 23, 59, 59); + assertEquals(86399000, (Object) new QTime(c.getTime()).getValue()); + + assertEquals(Integer.MIN_VALUE, (Object) new QTime((Date) null).getValue()); + } + + @Test + public void testQTimeToString() { + assertEquals("00:00:00.000", new QTime(0).toString()); + assertEquals("13:30:13.000", new QTime(48613000).toString()); + assertEquals("23:59:59.000", new QTime(86399000).toString()); + + assertEquals("0Nt", new QTime(Integer.MIN_VALUE).toString()); + } + + @Test + public void testQTimeFromString() { + assertEquals(new QTime(48613000), QTime.fromString("13:30:13.000")); + assertEquals(new QTime(0), QTime.fromString("00:00:00.000")); + assertEquals(new QTime(86399000), QTime.fromString("23:59:59.000")); + + assertEquals(new QTime(Integer.MIN_VALUE), QTime.fromString(null)); + assertEquals(new QTime(Integer.MIN_VALUE), QTime.fromString("")); + assertEquals(new QTime(Integer.MIN_VALUE), QTime.fromString("0Nt")); + } + + @Test + public void testQTimespan() { + final Calendar c = Calendar.getInstance(); + c.set(2000, 0, 1, 0, 0, 0); + c.set(Calendar.MILLISECOND, 0); + assertEquals(0L, (Object) new QTimespan(c.getTime()).getValue()); + c.set(2000, 0, 1, 13, 30, 13); + assertEquals(48613000000000L, (Object) new QTimespan(c.getTime()).getValue()); + c.set(2000, 0, 1, 23, 59, 59); + assertEquals(86399000000000L, (Object) new QTimespan(c.getTime()).getValue()); + + assertEquals(Long.MIN_VALUE, (Object) new QTimespan((Date) null).getValue()); + } + + @Test + public void testQTimespanToString() { + assertEquals("0D00:00:00.000000000", new QTimespan(0L).toString()); + assertEquals("0D13:30:13.000000000", new QTimespan(48613000000000L).toString()); + assertEquals("0D13:30:13.000000100", new QTimespan(48613000000100L).toString()); + assertEquals("-0D13:30:13.000001000", new QTimespan(-48613000001000L).toString()); + assertEquals("1D13:30:13.000000000", new QTimespan(135013000000000L).toString()); + assertEquals("0D23:59:59.000000000", new QTimespan(86399000000000L).toString()); + assertEquals("2D23:59:59.000000000", new QTimespan(259199000000000L).toString()); + assertEquals("-2D23:59:59.000000000", new QTimespan(-259199000000000L).toString()); + + assertEquals("0Nn", new QTimespan(Long.MIN_VALUE).toString()); + } + + @Test + public void testQTimespanFromString() { + assertEquals(new QTimespan(0L), QTimespan.fromString("0D00:00:00.000000000")); + assertEquals(new QTimespan(48613000000000L), QTimespan.fromString("0D13:30:13.000000000")); + assertEquals(new QTimespan(48613000000100L), QTimespan.fromString("0D13:30:13.000000100")); + assertEquals(new QTimespan(-48613000000000L), QTimespan.fromString("-0D13:30:13.000000000")); + assertEquals(new QTimespan(-48613000010000L), QTimespan.fromString("-0D13:30:13.000010000")); + assertEquals(new QTimespan(135013000000000L), QTimespan.fromString("1D13:30:13.000000000")); + assertEquals(new QTimespan(86399000000000L), QTimespan.fromString("0D23:59:59.000000000")); + assertEquals(new QTimespan(259199000000000L), QTimespan.fromString("2D23:59:59.000000000")); + assertEquals(new QTimespan(-259199000000000L), QTimespan.fromString("-2D23:59:59.000000000")); + + assertEquals(new QTimespan(Long.MIN_VALUE), QTimespan.fromString(null)); + assertEquals(new QTimespan(Long.MIN_VALUE), QTimespan.fromString("")); + assertEquals(new QTimespan(Long.MIN_VALUE), QTimespan.fromString("0Nn")); + } + + @Test + public void testQTimestamp() { + final Calendar c = Calendar.getInstance(); + c.set(1995, 6, 1, 13, 30, 13); + c.set(Calendar.MILLISECOND, 0); + assertEquals(-142079387000000000L, (Object) new QTimestamp(c.getTime()).getValue()); + c.set(1999, 0, 1, 23, 59, 59); + assertEquals(-31449601000000000L, (Object) new QTimestamp(c.getTime()).getValue()); + c.set(2000, 0, 1, 0, 0, 0); + assertEquals(0L, (Object) new QTimestamp(c.getTime()).getValue()); + c.set(2005, 6, 1, 1, 59, 59); + assertEquals(173498399000000000L, (Object) new QTimestamp(c.getTime()).getValue()); + c.set(2010, 0, 1, 14, 23, 42); + assertEquals(315671022000000000L, (Object) new QTimestamp(c.getTime()).getValue()); + + assertEquals(Long.MIN_VALUE, (Object) new QTimestamp((Date) null).getValue()); + } + + @Test + public void testQTimestampToString() { + assertEquals("1995.07.01D13:30:13.000000000", new QTimestamp(-142079387000000000L).toString()); + assertEquals("1999.01.01D23:59:59.000000000", new QTimestamp(-31449601000000000L).toString()); + assertEquals("2000.01.01D00:00:00.000000000", new QTimestamp(0L).toString()); + assertEquals("2005.07.01D01:59:59.000000012", new QTimestamp(173498399000000012L).toString()); + assertEquals("2010.01.01D14:23:42.000000066", new QTimestamp(315671022000000066L).toString()); + + assertEquals("0Np", new QTimestamp(Long.MIN_VALUE).toString()); + } + + @Test + public void testQTimestampFromString() { + assertEquals(new QTimestamp(-142079387000000000L), QTimestamp.fromString("1995.07.01D13:30:13.000000000")); + assertEquals(new QTimestamp(-31449601000000000L), QTimestamp.fromString("1999.01.01D23:59:59.000000000")); + assertEquals(new QTimestamp(0L), QTimestamp.fromString("2000.01.01D00:00:00.000000000")); + assertEquals(new QTimestamp(173498399000000012L), QTimestamp.fromString("2005.07.01D01:59:59.000000012")); + assertEquals(new QTimestamp(315671022000000066L), QTimestamp.fromString("2010.01.01D14:23:42.000000066")); + + assertEquals(new QTimestamp(Long.MIN_VALUE), QTimestamp.fromString(null)); + assertEquals(new QTimestamp(Long.MIN_VALUE), QTimestamp.fromString("")); + assertEquals(new QTimestamp(Long.MIN_VALUE), QTimestamp.fromString("0Np")); + } +} diff --git a/src/test/java/com/exxeleron/qjava/TestQReader.java b/src/test/java/com/exxeleron/qjava/TestQReader.java new file mode 100644 index 0000000..28a2466 --- /dev/null +++ b/src/test/java/com/exxeleron/qjava/TestQReader.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +public class TestQReader { + + private static final double DELTA = 0.1; + + @Test + public void testDeserialization() throws IOException, QException { + final QExpressions qe = new QExpressions("src/test/resources/QExpressions.out"); + + for ( final String expr : qe.getExpressions() ) { + final QWriter.ByteOutputStream writer = new QWriter.ByteOutputStream(); + final byte[] binaryExpr = qe.getBinaryExpression(expr); + writer.writeByte((byte) 1); // little endian + writer.writeByte((byte) 0); + writer.writeByte((byte) 0); + writer.writeByte((byte) 0); + writer.writeInt(binaryExpr.length + 8); + writer.write(binaryExpr); + writer.flush(); + + final QReader reader = new QReader(new DataInputStream(new ByteArrayInputStream(writer.toByteArray())), "ISO-8859-1"); + + try { + final Object obj = reader.read(false).getData(); + + if ( obj != null && obj.getClass().isArray() ) { + arrayEquals("Deserialization failed for q expression: " + expr, qe.getReferenceObject(expr), obj); + } else { + assertEquals("Deserialization failed for q expression: " + expr, qe.getReferenceObject(expr), obj); + } + } catch ( final QException e ) { + final Object ref = qe.getReferenceObject(expr); + if ( ref instanceof QException ) { + assertEquals("Deserialization failed for q expression: " + expr, ((QException) ref).getMessage(), e.getMessage()); + } else { + throw e; + } + } finally { + writer.close(); + } + } + } + + @Test + public void testCompressedDeserialization() throws IOException, QException { + final QExpressions qe = new QExpressions("src/test/resources/QCompressedExpressions.out"); + final Map reference = new HashMap(); + + final String[] q1000 = new String[1000]; + final Object[] q200 = new Object[] { new int[200], new int[200], new String[200] }; + for ( int i = 0; i < q1000.length; i++ ) { + q1000[i] = "q"; + } + for ( int i = 0; i < 200; i++ ) { + ((int[]) q200[0])[i] = i; + ((int[]) q200[1])[i] = i + 25; + ((String[]) q200[2])[i] = "a"; + } + + reference.put("1000#`q", q1000); + reference.put("([] q:1000#`q)", new QTable(new String[] { "q" }, new Object[] { q1000 })); + reference.put("([] a:til 200;b:25+til 200;c:200#`a)", new QTable(new String[] { "a", "b", "c" }, q200)); + + for ( final String expr : qe.getExpressions() ) { + final QWriter.ByteOutputStream writer = new QWriter.ByteOutputStream(); + final byte[] binaryExpr = qe.getBinaryExpression(expr); + writer.writeByte((byte) 1); // little endian + writer.writeByte((byte) 0); + writer.writeByte((byte) 1); // compressed + writer.writeByte((byte) 0); + writer.writeInt(binaryExpr.length + 8); + writer.write(binaryExpr); + writer.flush(); + + final QReader reader = new QReader(new DataInputStream(new ByteArrayInputStream(writer.toByteArray())), "ISO-8859-1"); + + final Object obj = reader.read(false).getData(); + + if ( obj != null && obj.getClass().isArray() ) { + arrayEquals("Deserialization failed for q expression: " + expr, reference.get(expr), obj); + } else { + assertEquals("Deserialization failed for q expression: " + expr, reference.get(expr), obj); + } + + writer.close(); + } + } + + private static void arrayEquals( final String message, final Object ref, final Object obj ) { + if ( obj instanceof Object[] && ref instanceof Object[] ) { + assertArrayEquals(message, (Object[]) ref, (Object[]) obj); + } else if ( obj instanceof byte[] && ref instanceof byte[] ) { + assertArrayEquals(message, (byte[]) ref, (byte[]) obj); + } else if ( obj instanceof short[] && ref instanceof short[] ) { + assertArrayEquals(message, (short[]) ref, (short[]) obj); + } else if ( obj instanceof int[] && ref instanceof int[] ) { + assertArrayEquals(message, (int[]) ref, (int[]) obj); + } else if ( obj instanceof long[] && ref instanceof long[] ) { + assertArrayEquals(message, (long[]) ref, (long[]) obj); + } else if ( obj instanceof char[] && ref instanceof char[] ) { + assertArrayEquals(message, (char[]) ref, (char[]) obj); + } else if ( obj instanceof float[] && ref instanceof float[] ) { + assertEquals(((float[]) ref).length, ((float[]) obj).length); + for ( int i = 0; i < ((float[]) ref).length; i++ ) { + assertEquals(((float[]) ref)[i], ((float[]) obj)[i], DELTA); + } + } else if ( obj instanceof double[] && ref instanceof double[] ) { + assertEquals(((double[]) ref).length, ((double[]) obj).length); + for ( int i = 0; i < ((double[]) ref).length; i++ ) { + assertEquals(((double[]) ref)[i], ((double[]) obj)[i], DELTA); + } + } else if ( obj instanceof boolean[] && ref instanceof boolean[] ) { + assertEquals(((boolean[]) ref).length, ((boolean[]) obj).length); + for ( int i = 0; i < ((boolean[]) ref).length; i++ ) { + assertEquals(((boolean[]) ref)[i], ((boolean[]) obj)[i]); + } + } else { + fail("Array type mismatch for: " + message); + } + } + +} diff --git a/src/test/java/com/exxeleron/qjava/TestQWriter.java b/src/test/java/com/exxeleron/qjava/TestQWriter.java new file mode 100644 index 0000000..c221791 --- /dev/null +++ b/src/test/java/com/exxeleron/qjava/TestQWriter.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * 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.exxeleron.qjava; + +import static org.junit.Assert.assertArrayEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.junit.Test; +import org.junit.internal.ArrayComparisonFailure; + +public class TestQWriter { + + @Test + public void testSerialization() throws IOException, QException { + final QExpressions qe = new QExpressions("src/test/resources/QExpressions.out"); + + for ( final String expr : qe.getExpressions() ) { + serializeObject(qe.getReferenceObject(expr), qe, expr); + + if ( qe.hasReferenceObjectAlt(expr) ) { + serializeObject(qe.getReferenceObjectAlt(expr), qe, expr); + } + } + } + + protected void serializeObject( final Object referenceObject, final QExpressions qe, final String expr ) throws IOException, QException, + ArrayComparisonFailure { + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + final QWriter writer = new QWriter(stream, "ISO-8859-1", 3); + writer.write(referenceObject, QConnection.MessageType.SYNC); + + final byte[] out = stream.toByteArray(); + + assertArrayEquals("Serialization failed for q expression: " + expr, qe.getBinaryExpression(expr), copyOfRange(out, 8, out.length)); + } + + public static byte[] copyOfRange( final byte[] original, final int from, final int to ) { + final int newLength = to - from; + if ( newLength < 0 ) { + throw new IllegalArgumentException(from + " > " + to); + } + final byte[] copy = new byte[newLength]; + System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); + return copy; + } +} diff --git a/src/test/resources/QCompressedExpressions.out b/src/test/resources/QCompressedExpressions.out new file mode 100644 index 0000000..9e24dec --- /dev/null +++ b/src/test/resources/QCompressedExpressions.out @@ -0,0 +1,6 @@ +1000#`q +DE070000800B00E80300007171FFAA7171FF7171FF7171FF7171FF2A7171FF7171FF7171BF +([] q:1000#`q) +EF070000006200630B0001000034007100000001020B00E803570000710071FF0071FF0071FF005571FF0071FF0071FF0071FF000171BE +([] a:til 200;b:25+til 200;c:200#`a) +FF070000006200630B00030000580061006201000000030206AC00C8000100020100010200017503020400010500010600000007A90000000800010900010A00014B0B0000000C0000000D00010E5500010F000110000111000112550001130001140001150001165500011700011800011900011A5500011B00011C00011D00011E5500011F000120000121000122550001230001240001250001265500012700012800012900012A5500012B00012C00012D00012E5500012F000130000131000132550001330001340001350001365500013700013800013900013A5500013B00013C00013D00013E5500013F000140000141000142550001430001440001450001465500014700014800014900014A5500014B00014C00014D00014E5500014F000150000151000152550001530001540001550001565500015700015800015900015A5500015B00015C00015D00015EF500015F0001600001610000006200A5000063000000640001650001AA660001670001680001690001AA6A00016B00016C00016D0001AA6E00016F0001700001710001AA720001730001740001750001AA760001770001780001790001AA7A00017B00017C00017D0001AA7E00017F0001800001810001AA820001830001840001850001AA860001870001880001890001AA8A00018B00018C00018D0001AA8E00018F0001900001910001AA920001930001940001950001AA960001970001980001990001AA9A00019B00019C00019D0001AA9E00019F0001A00001A10001AAA20001A30001A40001A50001AAA60001A70001A80001A90001AAAA0001AB0001AC0001AD0001AAAE0001AF0001B00001B10001AAB20001B30001B40001B50001AAB60001B70001B80001B90001AABA0001BB0001BC0001BD0001AABE0001BF0001C00001C10001AAC20001C30001C40001C50001FAC60001C700010600C80219FF0001AF5AFF00019BB2C802C90001CA0001AACB0001CC0001CD0001CE0001AACF0001D00001D10001D20001AAD30001D40001D50001D60001AAD70001D80001D90001DA0001AADB0001DC0001DD0001DE0001FADF0001E000010B00C802610061FF0200618A diff --git a/src/test/resources/QExpressions.out b/src/test/resources/QExpressions.out new file mode 100644 index 0000000..831e16c --- /dev/null +++ b/src/test/resources/QExpressions.out @@ -0,0 +1,161 @@ +1+` +807479706500 +1 +FA01000000 +1i +FA01000000 +-234h +FB16FF +0b +FF00 +1b +FF01 +0x2a +FC2A +89421099511627575j +F937BFD02704B03D01 +3.234 +F7AC1C5A643BDF0940 +5.5e +F80000B040 +"0" +F630 +"abc" +0A0003000000616263 +"" +0A0000000000 +"quick brown fox jumps over a lazy dog" +0A0025000000717569636B2062726F776E20666F78206A756D7073206F7665722061206C617A7920646F67 +`abc +F561626300 +`quickbrownfoxjumpsoveralazydog +F5717569636B62726F776E666F786A756D70736F766572616C617A79646F6700 +2000.01.04D05:36:57.600 +F400C0CAFA20FE0000 +2001.01m +F30C000000 +2001.01.01 +F26E010000 +2000.05.01 +F279000000 +2000.01.04T05:36:57.600 +F1AC1C5A643BDF0940 +0D05:36:57.600 +F000C0DD4663120000 +12:01 +EFD1020000 +12:05:00 +EEECA90000 +12:04:59.123 +ED73BE9702 +0x00 +FC00 +0Nh +FB0080 +0N +FA00000080 +0Ni +FA00000080 +0Nj +F90000000000000080 +0Ne +F80000C07F +0n +F7000000000000F87F +" " +F620 +` +F500 +0Np +F40000000000000080 +0Nm +F300000080 +0Nd +F200000080 +0Nz +F1000000000000F87F +0Nn +F00000000000000080 +0Nu +EF00000080 +0Nv +EE00000080 +0Nt +ED00000080 +() +000000000000 +(0b;1b;0b) +010003000000000100 +(0x01;0x02;0xff) +0400030000000102FF +(1h;2h;3h) +050003000000010002000300 +1 2 3 +060003000000010000000200000003000000 +(1i;2i;3i) +060003000000010000000200000003000000 +(1j;2j;3j) +070003000000010000000000000002000000000000000300000000000000 +(5.5e; 8.5e) +0800020000000000B04000000841 +3.23 6.46 +090002000000D7A3703D0AD70940D7A3703D0AD71940 +(1;`bcd;"0bc";5.5e) +000004000000FA01000000F5626364000A0003000000306263F80000B040 +(enlist 1h; 2; enlist 3j) +0000030000000500010000000100FA020000000700010000000300000000000000 +`the`quick`brown`fox +0B000400000074686500717569636B0062726F776E00666F7800 +2000.01.04D05:36:57.600 0Np +0C000200000000C0CAFA20FE00000000000000000080 +(2001.01m; 0Nm) +0D00020000000C00000000000080 +2001.01.01 2000.05.01 0Nd +0E00030000006E0100007900000000000080 +2000.01.04T05:36:57.600 0Nz +0F0002000000AC1C5A643BDF0940000000000000F87F +0D05:36:57.600 0Nn +10000200000000C0DD46631200000000000000000080 +12:01 0Nu +110002000000D102000000000080 +12:05:00 0Nv +120002000000ECA9000000000080 +12:04:59.123 0Nt +13000200000073BE970200000080 +:: +6500 +{x+y} +64000A00050000007B782B797D +{x+y}[3] +680200000064000A00050000007B782B797DFA03000000 +(enlist `a)!(enlist 1) +630B0001000000610006000100000001000000 +1 2!`abc`cdefgh +6306000200000001000000020000000B00020000006162630063646566676800 +(1;2h;3.3;"4")!(`one;2 3;"456";(7;8 9)) +63000004000000FA01000000FB0200F76666666666660A40F634000004000000F56F6E650006000200000002000000030000000A0003000000343536000002000000FA070000000600020000000800000009000000 +(0 1; 2 3)!`first`second +63000002000000060002000000000000000100000006000200000002000000030000000B00020000006669727374007365636F6E6400 +`A`B`C!((1;2.2;3);(`x`y!(`a;2));5.5) +630B0003000000410042004300000003000000000003000000FA01000000F79A99999999990140FA03000000630B000200000078007900000002000000F56100FA02000000F70000000000001640 +flip `abc`def!(1 2 3; 4 5 6) +6200630B00020000006162630064656600000002000000060003000000010000000200000003000000060003000000040000000500000006000000 +flip `name`iq!(`Dent`Beeblebrox`Prefect;98 42 126) +6200630B00020000006E616D65006971000000020000000B000300000044656E7400426565626C6562726F78005072656665637400060003000000620000002A0000007E000000 +([] sc:1 2 3; nsc:(1 2; 3 4; 5 6 7)) +6200630B00020000007363006E73630000000200000006000300000001000000020000000300000000000300000006000200000001000000020000000600020000000300000004000000060003000000050000000600000007000000 +([] name:`symbol$(); iq:`int$()) +6200630B00020000006E616D65006971000000020000000B0000000000060000000000 +([] pos:`d1`d2`d3;dates:(2001.01.01;2000.05.01;0Nd)) +6200630B0002000000706F73006461746573000000020000000B00030000006431006432006433000E00030000006E0100007900000000000080 +([eid:1001 1002 1003] pos:`d1`d2`d3;dates:(2001.01.01;2000.05.01;0Nd)) +636200630B000100000065696400000001000000060003000000E9030000EA030000EB0300006200630B0002000000706F73006461746573000000020000000B00030000006431006432006433000E00030000006E0100007900000000000080 +0Ng +fe00000000000000000000000000000000 +"G"$"00000000-0000-0000-0000-000000000000" +fe00000000000000000000000000000000 +"G"$"0162fca8-c01f-7450-208c-55a06b2f2c54" +fe0162fca8c01f7450208c55a06b2f2c54 +("G"$"0162fca8-c01f-7450-208c-55a06b2f2c54"; 0Ng) +0200020000000162fca8c01f7450208c55a06b2f2c5400000000000000000000000000000000 +