From 12c0fbba88a46a83f8a23efe7200f2dea9ddca75 Mon Sep 17 00:00:00 2001 From: Jim Cortez Date: Wed, 23 May 2012 15:19:12 -0700 Subject: [PATCH] Initial import of Java library, Hello World\! --- LICENSE | 32 + README.txt | 57 + build.properties | 20 + examples/AppChatExample/AppChatExample.java | 216 ++ examples/AppChatExample/README | 7 + examples/SimpleNavigationExample/README | 7 + .../SimpleExample.java | 191 ++ examples/VideoListExample/README | 7 + .../VideoListExample/VideoListExample.java | 242 ++ pom.xml | 96 + .../connectedtv/ycommand/AbstractCommand.java | 264 ++ .../ycommand/AbstractInputCommand.java | 68 + .../ycommand/AbstractMethodCommand.java | 170 ++ .../ycommand/AbstractServiceCommand.java | 68 + .../ycommand/AbstractSessionCommand.java | 68 + .../ycommand/AuthSessionCommand.java | 186 ++ .../yahoo/connectedtv/ycommand/Base64.java | 2202 +++++++++++++++++ .../ycommand/CommandParseException.java | 62 + .../connectedtv/ycommand/CommandParser.java | 96 + .../connectedtv/ycommand/CommandRouter.java | 259 ++ .../connectedtv/ycommand/CommandSchema.java | 276 +++ .../connectedtv/ycommand/Connection.java | 562 +++++ .../ycommand/CreateSessionCommand.java | 209 ++ .../ycommand/EasyX509TrustManager.java | 82 + .../connectedtv/ycommand/ErrorCommand.java | 132 + .../ycommand/GrantedSessionCommand.java | 99 + .../ycommand/ICommandSubscriber.java | 56 + .../ycommand/IConnectionHandler.java | 66 + .../connectedtv/ycommand/IPCodeResolver.java | 168 ++ .../ycommand/KeyboardInputCommand.java | 473 ++++ .../ycommand/MediaControlInputCommand.java | 336 +++ .../ycommand/MediaLaunchServiceCommand.java | 204 ++ .../ycommand/NavigationInputCommand.java | 228 ++ .../ycommand/ResetSessionCommand.java | 89 + .../ycommand/StatusSessionCommand.java | 118 + .../ycommand/SubscribedCommand.java | 90 + .../ycommand/UnsubscribedCommand.java | 85 + .../yahoo/connectedtv/ycommand/Utilities.java | 123 + .../ycommand/VideolLaunchMethodCommand.java | 285 +++ .../connectedtv/ycommand/WidgetCommand.java | 167 ++ .../ycommand/WidgetLaunchMethodCommand.java | 151 ++ .../ycommand/WidgetListServiceCommand.java | 181 ++ .../ycommand/WidgetMethodCommand.java | 104 + .../yahoo/connectedtv/ycommand/package.html | 14 + .../ycommand/AuthSessionCommandTest.java | 99 + .../ycommand/CommandParserTest.java | 94 + .../ycommand/CommandSchemaTest.java | 93 + .../ycommand/CreateSessionCommandTest.java | 73 + .../ycommand/ErrorCommandTest.java | 59 + .../ycommand/IPCodeResolverTest.java | 108 + .../ycommand/KeyboardInputCommandTest.java | 133 + .../MediaControlInputCommandTest.java | 104 + .../MediaLaunchServiceCommandTest.java | 66 + .../ycommand/NavigationInputCommandTest.java | 91 + .../ycommand/SubscribedCommandTest.java | 79 + .../ycommand/UnsubscribedCommandTest.java | 80 + .../VideolLaunchMethodCommandTest.java | 110 + .../ycommand/WidgetCommandTest.java | 67 + .../WidgetLaunchMethodCommandTest.java | 119 + .../ycommand/WidgetListCommandTest.java | 63 + 60 files changed, 10054 insertions(+) create mode 100644 LICENSE create mode 100644 README.txt create mode 100644 build.properties create mode 100644 examples/AppChatExample/AppChatExample.java create mode 100644 examples/AppChatExample/README create mode 100644 examples/SimpleNavigationExample/README create mode 100644 examples/SimpleNavigationExample/SimpleExample.java create mode 100644 examples/VideoListExample/README create mode 100644 examples/VideoListExample/VideoListExample.java create mode 100644 pom.xml create mode 100644 src/com/yahoo/connectedtv/ycommand/AbstractCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/AbstractInputCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/AbstractMethodCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/AbstractServiceCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/AbstractSessionCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/AuthSessionCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/Base64.java create mode 100644 src/com/yahoo/connectedtv/ycommand/CommandParseException.java create mode 100644 src/com/yahoo/connectedtv/ycommand/CommandParser.java create mode 100644 src/com/yahoo/connectedtv/ycommand/CommandRouter.java create mode 100644 src/com/yahoo/connectedtv/ycommand/CommandSchema.java create mode 100644 src/com/yahoo/connectedtv/ycommand/Connection.java create mode 100644 src/com/yahoo/connectedtv/ycommand/CreateSessionCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/EasyX509TrustManager.java create mode 100644 src/com/yahoo/connectedtv/ycommand/ErrorCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/GrantedSessionCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/ICommandSubscriber.java create mode 100644 src/com/yahoo/connectedtv/ycommand/IConnectionHandler.java create mode 100644 src/com/yahoo/connectedtv/ycommand/IPCodeResolver.java create mode 100644 src/com/yahoo/connectedtv/ycommand/KeyboardInputCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/MediaControlInputCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/MediaLaunchServiceCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/NavigationInputCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/ResetSessionCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/StatusSessionCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/SubscribedCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/UnsubscribedCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/Utilities.java create mode 100644 src/com/yahoo/connectedtv/ycommand/VideolLaunchMethodCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/WidgetCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/WidgetLaunchMethodCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/WidgetListServiceCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/WidgetMethodCommand.java create mode 100644 src/com/yahoo/connectedtv/ycommand/package.html create mode 100644 tests/com/yahoo/connectedtv/ycommand/AuthSessionCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/CommandParserTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/CommandSchemaTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/CreateSessionCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/ErrorCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/IPCodeResolverTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/KeyboardInputCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/MediaControlInputCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/MediaLaunchServiceCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/NavigationInputCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/SubscribedCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/UnsubscribedCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/VideolLaunchMethodCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/WidgetCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/WidgetLaunchMethodCommandTest.java create mode 100644 tests/com/yahoo/connectedtv/ycommand/WidgetListCommandTest.java diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a4a1c80 --- /dev/null +++ b/LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 2011, Yahoo! Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Yahoo! Inc. nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of Yahoo! Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..c12cd8d --- /dev/null +++ b/README.txt @@ -0,0 +1,57 @@ +OVERVIEW +******** +The Yahoo! Connected TV Device Communication Java Library uses several 3rd party open source libraries and tools. This file summarizes the tools used, their purpose, and the licenses under which they are released. This file also explains how to build the jar file and generate Javadoc documentation. + +BUILD +***** +To create a jar for this library use Maven (http://maven.apache.org/). Use the command: + + mvn package + +This will download all dependencies and create the jar file "target/device-control-.jar". + +DOCUMENTATION +************* +If you wish to create documentation from the source use this command: + + mvn "javadoc:javadoc" + +Generated documentation will be created in the doc/ folder. Open the doc/index.html file in your web browser. + +LIBRARIES +********* +Except as specifically stated below, the 3rd party software packages are not distributed as part of this project, but instead are separately downloaded from their respective provider and built on the developer's machine prior to building the library. + +* JmDNS version 3.4.0 (Apache 2.0 license) +(A mDNS client library) +http://jmdns.sourceforge.net/ + +* org.json version 20090211 +(Java JSON Library) +http://json.org/java/ + +For unit tests: +* Junit version 4.8.2 +(Java testing library) +http://junit.sourceforge.net/ + +* Hamcrest version 1.3.RC2 +(Java unit test helper library) +http://code.google.com/p/hamcrest/ + +LICENSE +******* +The Yahoo! Connected TV Device Communication Java Library is licensed under the following BSD License. + +Software License Agreement (BSD License) +Copyright c 2011 Yahoo! Inc. All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of Yahoo! Inc. nor the names of Yahoo! Connected TV's contributors may be used to endorse or promote products derived from this software without specific prior written permission of Yahoo! Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/build.properties b/build.properties new file mode 100644 index 0000000..0749e26 --- /dev/null +++ b/build.properties @@ -0,0 +1,20 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +# The name of your application package as defined in the manifest. +# Used by the 'uninstall' rule. +application.package=com.yahoo.connectedtv.ycommand diff --git a/examples/AppChatExample/AppChatExample.java b/examples/AppChatExample/AppChatExample.java new file mode 100644 index 0000000..8f49e05 --- /dev/null +++ b/examples/AppChatExample/AppChatExample.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +/*** + * This example demonstrates how to communicate with a specific TV app. It allows an IM-type message passing. Type in + * the console and your message will be printed on the TV screen. Type something on the TV and it will show up in the + * console. + */ + +import com.yahoo.connectedtv.ycommand.*; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class AppChatExample { + public static String HOST = "localhost"; + public static int PORT = 8099; + public static String APP_NAME = "AppChatExample"; + public final static String APP_ID = "0xeTgF3c"; + public final static String CONSUMER_KEY = "dj0yJmk9T1Y0MmVIWWEzWVc3JmQ9WVdrOU1IaGxWR2RHTTJNbWNHbzlNVEUzTkRFM09ERTJNZy0tJnM9Y29uc3VtZXJzZWNyZXQmeD0yNA--"; + public final static String SECRET = "1b8f0feb4d8d468676293caa769e19958bf36843"; + + public static String WIDGET_ID = "com.yahoo.connectedtv.examples.appchat.widget"; + + public Connection conn; + + private CommandRouter router; + + public AppChatExample(String host, int port) { + // Set up our router object, this is responsible for routing incoming + // and outgoing messages + router = new CommandRouter(); + try { + // setup our socket connection to the tv, but don't connect yet + conn = new Connection(InetAddress.getByName(host), port, router); + + // Tell out router which network connection to use + router.setConnection(conn); + + // setup a handler for incoming GrantedSessionCommand message + // objects + router.registerCommandSubscriber(new CommandSubscriber(), GrantedSessionCommand.class); + + // Establish a connection + conn.establish(); + // Since this is the first time we are connecting, we must + // create a new session + router.publishCommand(new CreateSessionCommand(APP_ID, CONSUMER_KEY, SECRET, APP_NAME)); + + String message = this.getUserInput("Code: "); + + router.publishCommand(new AuthSessionCommand(message, conn.getPeerCertificate())); + } catch (UnknownHostException e) { + this.exitWithError("Error resolving " + host); + } catch (IOException e) { + this.exitWithError("Problem writing to the network connection"); + } catch (InterruptedException e) { + this.exitWithError("Problem writing to the network connection"); + } + } + + private String getUserInput(String prompt) { + System.out.print(prompt); + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + try { + return br.readLine().trim(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + private class CommandSubscriber implements ICommandSubscriber { + public void onCommandReceived(AbstractCommand command) { + // Filter out the messages we care about + if (command instanceof GrantedSessionCommand) { + // Print our our unique key, this will be used for + // subsequent connections + Utilities.log("Your instanceId is " + ((GrantedSessionCommand) command).getInstanceId()); + + try { + router.registerCommandSubscriber(this, WidgetCommand.class, WIDGET_ID); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + AppChatExample.this.startUserInput(); + + //Launch widget + try { + AppChatExample.this.router.publishCommand(new WidgetLaunchMethodCommand("callid-launch-" + WIDGET_ID, WIDGET_ID)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else if (command instanceof WidgetCommand) { + // handle incoming message from a widget + try { + JSONObject payload = new JSONObject(command.getPayload()); + String username = payload.getString("username"); + String message = payload.getString("message"); + Utilities.log(username + ": " + message); + } catch (JSONException e) { + e.printStackTrace(); + } + } else { // print out the others for educational purposes + Utilities.log("Received: " + command.toString()); + } + } + + public void onConnectionLost(Connection conn) { + Utilities.log("Connection Lost!"); + // AppChatExample.this.closeConnectionAndExit(); + + synchronized (AppChatExample.this.conn) { + AppChatExample.this.conn.notifyAll(); + } + } + } + + private void startUserInput() { + (new Thread() { + public void run() { + try { + while (true) { + String message = AppChatExample.this.getUserInput("Message: "); + + if (message == null || message.equals("")) { + continue; + } else if (message.equals("q") || message.equals("quit")) { + break; + } + + JSONObject msgObj = new JSONObject(); + msgObj.put("message", message); + msgObj.put("username", "You"); + AppChatExample.this.router.publishCommand(new WidgetCommand(WIDGET_ID, msgObj)); + } + AppChatExample.this.closeConnectionAndExit(); + + } catch (JSONException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + } + + private void closeConnectionAndExit() { + this.conn.close(); + + // Notify the main thread that everything we wanted to + // do is done. + synchronized (this.conn) { + this.conn.notifyAll(); + } + } + + private void exitWithError(String message) { + Utilities.log(message); + System.exit(-1); + } + + public static void main(String[] args) { + AppChatExample chatExample = new AppChatExample(HOST, PORT); + + // Let's wait until everything is done. This thread will get + // notified once we are ready to clean up + synchronized (chatExample.conn) { + try { + chatExample.conn.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + System.exit(1); + } +} diff --git a/examples/AppChatExample/README b/examples/AppChatExample/README new file mode 100644 index 0000000..2eb11cb --- /dev/null +++ b/examples/AppChatExample/README @@ -0,0 +1,7 @@ +This example opens a connection, then starts an IM-like dialog with the Chat example. + +Java: +compiling: +javac -cp AppChatExample +running: +java -cp ":" AppChatExample diff --git a/examples/SimpleNavigationExample/README b/examples/SimpleNavigationExample/README new file mode 100644 index 0000000..3574447 --- /dev/null +++ b/examples/SimpleNavigationExample/README @@ -0,0 +1,7 @@ +This example opens a connection, issues a command to navigate right, then closes. + +Java: +compiling: +javac -cp SimpleExample +running: +java -cp ":" SimpleExample diff --git a/examples/SimpleNavigationExample/SimpleExample.java b/examples/SimpleNavigationExample/SimpleExample.java new file mode 100644 index 0000000..3a6a27e --- /dev/null +++ b/examples/SimpleNavigationExample/SimpleExample.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +import com.yahoo.connectedtv.ycommand.CommandRouter; + +/*** + * This Example is very simple. It establishes a connection to the TV, then sends a series of navigation commands + * that makes the application dock do a little dance. + */ + +import com.yahoo.connectedtv.ycommand.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.UnknownHostException; + +public class SimpleExample { + public static String HOST = "localhost"; + public static int PORT = 8099; + public static String APP_NAME = "SimpleExample"; + public final static String APP_ID = "0xeTgF3c"; + public final static String CONSUMER_KEY = "dj0yJmk9T1Y0MmVIWWEzWVc3JmQ9WVdrOU1IaGxWR2RHTTJNbWNHbzlNVEUzTkRFM09ERTJNZy0tJnM9Y29uc3VtZXJzZWNyZXQmeD0yNA--"; + public final static String SECRET = "1b8f0feb4d8d468676293caa769e19958bf36843"; + + public static CommandRouter router; + public static Connection conn; + + public static void main(String[] args) { + // Set up our router object, this is responsible for routing incoming + // and outgoing messages + router = new CommandRouter(); + try { + // setup our socket connection to the tv, but don't connect yet + conn = new Connection(HOST, PORT, router); + + // Tell out router which network connection to use + router.setConnection(conn); + + // setup a handler for incoming GrantedSessionCommand message + // objects + try { + router.registerCommandSubscriber(new ICommandSubscriber() { + public void onCommandReceived(AbstractCommand command) { + // Filter out the messages we care about + if (command instanceof GrantedSessionCommand) { + // Print our our unique key, this will be used for + // subsequent connections + Utilities.log("Your instanceId is " + ((GrantedSessionCommand) command).getInstanceId()); + + + try { + //let's make the dock show up on the TV + router.publishCommand(new NavigationInputCommand("press_yahoo")); + + Thread.sleep(2000); //sleep for 2 seconds so the animation to dock finishes + + // Lets do something cool, like tell the TV to navigate to the right. Then do a little dance + // This is the same as pressing "right" on your remote + router.publishCommand(new NavigationInputCommand("press_right")); + + Thread.sleep(1000); + + // slide to the left + router.publishCommand(new NavigationInputCommand("press_left")); + + Thread.sleep(1000); + + //slide to the right + router.publishCommand(new NavigationInputCommand("press_right")); + + Thread.sleep(1000); + + //take it back now, y'all + router.publishCommand(new NavigationInputCommand("press_left")); + + //cha cha cha + router.publishCommand(new NavigationInputCommand("press_up")); + Thread.sleep(1000); + router.publishCommand(new NavigationInputCommand("press_down")); + Thread.sleep(1000); + router.publishCommand(new NavigationInputCommand("press_up")); + Thread.sleep(1000); + router.publishCommand(new NavigationInputCommand("press_down")); + } catch (InterruptedException e) { + Utilities.log("Problem writing to the network connection"); + conn.close(); + System.exit(-1); + } + + // Notify the main thread that everything we wanted to + // do is done. + synchronized (conn) { + conn.notifyAll(); + } + } else { // print out the others for educational purposes + Utilities.log("Received: " + command.toString()); + } + } + + public void onConnectionLost(Connection conn) { + Utilities.log("Connection Lost!"); + } + }, GrantedSessionCommand.class); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + try { + // Establish a connection + conn.establish(); + // Since this is the first time we are connecting, we must + // create a new session + router.publishCommand(new CreateSessionCommand(APP_ID, CONSUMER_KEY, SECRET, APP_NAME)); + + String message = getUserInput("Code: "); + router.publishCommand(new AuthSessionCommand(message, conn.getPeerCertificate())); + + // Let's wait until everything is done. This thread will get + // notified once we are ready to clean up + try { + synchronized (conn) { + conn.wait(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } catch (IOException ioe) { + Utilities.log("Error establishing connection to " + HOST + ":" + PORT); + } catch (InterruptedException e) { + Utilities.log("Error establishing connection to " + HOST + ":" + PORT); + } + + // It is always good practice to clean up after yourself + conn.close(); + + } catch (UnknownHostException e) { + Utilities.log("Error resolving " + HOST); + System.exit(-1); + } catch (IOException e) { + Utilities.log("Problem writing to the network connection"); + e.printStackTrace(); + System.exit(-1); + } + + System.exit(1); + } + + private static String getUserInput(String prompt) { + System.out.print(prompt); + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + try { + return br.readLine().trim(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/examples/VideoListExample/README b/examples/VideoListExample/README new file mode 100644 index 0000000..2480f23 --- /dev/null +++ b/examples/VideoListExample/README @@ -0,0 +1,7 @@ +This example opens a connection, queries the VideoListExample widget for a list of videos, then prompts the user to enter a URL. + +Java: +compiling: +javac -cp VideoListExample +running: +java -cp ":" VideoListExample diff --git a/examples/VideoListExample/VideoListExample.java b/examples/VideoListExample/VideoListExample.java new file mode 100644 index 0000000..542e90b --- /dev/null +++ b/examples/VideoListExample/VideoListExample.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +/*** + * This example demonstrates how to build in video playing handling. It pairs up with the "App<->Device Video List" + * example that can be found in the simulator store. Once connected, it fetches a list of videos from the app and lists + * them out. You can enter in any URL and it will play the video in the simulator. + */ + +import com.yahoo.connectedtv.ycommand.*; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class VideoListExample { + public static String HOST = "localhost"; + public static int PORT = 8099; + public static String APP_NAME = "SimpleExample"; + public final static String APP_ID = "0xeTgF3c"; + public final static String CONSUMER_KEY = "dj0yJmk9T1Y0MmVIWWEzWVc3JmQ9WVdrOU1IaGxWR2RHTTJNbWNHbzlNVEUzTkRFM09ERTJNZy0tJnM9Y29uc3VtZXJzZWNyZXQmeD0yNA--"; + public final static String SECRET = "1b8f0feb4d8d468676293caa769e19958bf36843"; + + public static String WIDGET_ID = "com.yahoo.connectedtv.examples.dcvideolist"; + public static String METHOD_NAME = "fetchVideos"; + public static String CALL_ID = "ireallyshouldbeunique"; + + public Connection conn; + + private CommandRouter router; + + private CommandSubscriber commandHandler; + + public VideoListExample(String host, int port) { + // Set up our router object, this is responsible for routing incoming + // and outgoing messages + router = new CommandRouter(); + try { + // setup our socket connection to the tv, but don't connect yet + conn = new Connection(InetAddress.getByName(host), port, router); + + // Tell out router which network connection to use + router.setConnection(conn); + + // setup a handler for incoming GrantedSessionCommand message + // objects + this.commandHandler = new CommandSubscriber(); + router.registerCommandSubscriber(this.commandHandler, GrantedSessionCommand.class); + + // Establish a connection + conn.establish(); + // Since this is the first time we are connecting, we must + // create a new session + router.publishCommand(new CreateSessionCommand(APP_ID, CONSUMER_KEY, SECRET, APP_NAME)); + + // type in the 4 digit code on the screen + String message = this.getUserInput("Code: "); + + AuthSessionCommand asc = new AuthSessionCommand(message, conn.getPeerCertificate()); + + router.publishCommand(asc); + } catch (UnknownHostException e) { + this.exitWithError("Error resolving " + HOST); + } catch (IOException e) { + this.exitWithError("Problem writing to the network connection"); + } catch (InterruptedException e) { + this.exitWithError("Problem writing to the network connection"); + } + } + + private String getUserInput(String prompt) { + System.out.print(prompt); + InputStreamReader in = new InputStreamReader(System.in); + BufferedReader br = new BufferedReader(in); + try { + return br.readLine().trim(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + private class CommandSubscriber implements ICommandSubscriber { + public void onCommandReceived(AbstractCommand command) { + // Filter out the messages we care about + if (command instanceof GrantedSessionCommand) { + // Print our our unique key, this will be used for subsequent connections + Utilities.log("Your instanceId is " + ((GrantedSessionCommand) command).getInstanceId()); + + //register for commands from the widget + try { + router.registerCommandSubscriber(VideoListExample.this.commandHandler, WidgetCommand.class, WIDGET_ID); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + VideoListExample.this.startUserInput(); + } else if (command instanceof WidgetCommand) { + // handle incoming message from a widget + VideoListExample.this.handleWidgetMessage((WidgetCommand) command); + } else if (command instanceof WidgetMethodCommand) { + try { + JSONArray videos = new JSONArray(command.getPayload()); + for (int i = 0; i < videos.length(); i++) { + JSONObject video = videos.getJSONObject(i); + System.out.println(i + ". Video: " + video.getString("title")); + System.out.println("\tdescription: " + video.getString("description")); + System.out.println("\tthumbnail: " + video.getString("thumbnail")); + System.out.println("\turl: " + video.getString("url")); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + public void onConnectionLost(Connection conn) { + Utilities.log("Connection Lost!"); + + synchronized (VideoListExample.this.conn) { + VideoListExample.this.conn.notifyAll(); + } + } + } + + private void startUserInput() { + router.registerRMCCommandSubscriber(WIDGET_ID, METHOD_NAME, CALL_ID, this.commandHandler); + + try { + this.router.publishCommand(new WidgetMethodCommand(AbstractCommand.METHOD_CALL, METHOD_NAME, CALL_ID, WIDGET_ID, null)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + (new Thread() { + public void run() { + try { + while (true) { + String url = VideoListExample.this.getUserInput("URL to play (q to quit): "); + + if (url == null || url.equals("")) { + continue; + } else if (url.equals("q") || url.equals("quit")) { + break; + } + + JSONObject msgObj = new JSONObject(); + msgObj.put("url", url); + msgObj.put("method", "launchvideo"); + VideoListExample.this.router.publishCommand(new WidgetLaunchMethodCommand("callid-" + url, WIDGET_ID, msgObj.toString())); + } + VideoListExample.this.closeConnectionAndExit(); + + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (JSONException e) { + e.printStackTrace(); + } + } + }).start(); + + } + + private void handleWidgetMessage(WidgetCommand command) { + try { + JSONObject payload = new JSONObject(command.getPayload()); + String username = payload.getString("username"); + String message = payload.getString("message"); + Utilities.log(username + ": " + message); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + private void closeConnectionAndExit() { + this.conn.close(); + + // Notify the main thread that everything we wanted to + // do is done. + synchronized (this.conn) { + this.conn.notifyAll(); + } + } + + private void exitWithError(String message) { + Utilities.log(message); + System.exit(-1); + } + + public static void main(String[] args) { + VideoListExample chatExample = new VideoListExample(HOST, PORT); + + // Let's wait until everything is done. This thread will get + // notified once we are ready to clean up + try { + synchronized (chatExample.conn) { + chatExample.conn.wait(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.exit(1); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..55826c5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + com.yahoo.connectedtv + device-control + Yahoo! Connected TV Device Control + 1001.1.17 + http://connectedtv.yahoo.com + 2011 + + + jecortez + Jim Cortez + tvwidgets@yahoo.com + http://connectedtv.yahoo.com + Yahoo! + http://yahoo.com + (GMT-08:00) Pacific Time(US & Canada) + + + + + + + src + tests + device-control + + + maven-compiler-plugin + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-javadoc-plugin + + ${basedir} + doc + ${basedir}/src/overview.html + false + + Yahoo!, Inc. + + ]]> + + true + + http://download.oracle.com/javase/6/docs/api/ + http://www.json.org/javadoc/ + + + + + attach-javadocs + + jar + + + + + + + + + org.json + json + 20090211 + false + + + org.hamcrest + hamcrest-core + 1.3.RC2 + + + junit + junit-dep + 4.8.2 + + + org.hamcrest + hamcrest-core + + + + + diff --git a/src/com/yahoo/connectedtv/ycommand/AbstractCommand.java b/src/com/yahoo/connectedtv/ycommand/AbstractCommand.java new file mode 100644 index 0000000..eea5179 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/AbstractCommand.java @@ -0,0 +1,264 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * Base Command class. + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public abstract class AbstractCommand implements Serializable { + /** + * Field serialVersionUID. + * (value is 1) + */ + private static final long serialVersionUID = 1L; // must be overridden + /** + * Field METHOD_PUBLISH. + * (value is ""PUBLISH"") + */ + public final static String METHOD_PUBLISH = "PUBLISH"; + /** + * Field METHOD_SUBSCRIBE. + * (value is ""SUBSCRIBE"") + */ + public final static String METHOD_SUBSCRIBE = "SUBSCRIBE"; + /** + * Field METHOD_SUBSCRIBED. + * (value is ""SUBSCRIBED"") + */ + public final static String METHOD_SUBSCRIBED = "SUBSCRIBED"; + /** + * Field METHOD_UNSUBSCRIBE. + * (value is ""UNSUBSCRIBE"") + */ + public final static String METHOD_UNSUBSCRIBE = "UNSUBSCRIBE"; + /** + * Field METHOD_UNSUBSCRIBED. + * (value is ""UNSUBSCRIBED"") + */ + public final static String METHOD_UNSUBSCRIBED = "UNSUBSCRIBED"; + /** + * Field METHOD_CALL. + * (value is ""CALL"") + */ + public final static String METHOD_CALL = "CALL"; + /** + * Field METHOD_RETURN. + * (value is ""RETURN"") + */ + public final static String METHOD_RETURN = "RETURN"; + /** + * Field TYPE_INPUT. + * (value is ""INPUT"") + */ + public final static String TYPE_INPUT = "INPUT"; + /** + * Field TYPE_WIDGET. + * (value is ""WIDGET"") + */ + public final static String TYPE_WIDGET = "WIDGET"; + /** + * Field TYPE_SERVICE. + * (value is ""SERVICE"") + */ + public final static String TYPE_SERVICE = "SERVICE"; + + /** + * Field DIRECTIVE_SESSION. + * (value is ""SESSION"") + */ + public final static String DIRECTIVE_SESSION = "SESSION"; + /** + * Field DIRECTIVE_ERROR. + * (value is ""ERROR"") + */ + public final static String DIRECTIVE_ERROR = "ERROR"; + + /** + * Field method. + */ + protected String method; + /** + * Field type. + */ + protected String type; + /** + * Field subject. + */ + protected String subject; + + /** + * create a command from an already-tokenized command + * + * @param tokenizedCommand + * a tokenized command (typically from command Parser) + + * @throws CommandParseException */ + public AbstractCommand(String[] tokenizedCommand) throws CommandParseException { + if (tokenizedCommand != null + && tokenizedCommand.length > 0 + && (tokenizedCommand[0].equals(AbstractCommand.METHOD_PUBLISH) + || tokenizedCommand[0].equals(AbstractCommand.METHOD_SUBSCRIBE) + || tokenizedCommand[0].equals(AbstractCommand.METHOD_SUBSCRIBED) + || tokenizedCommand[0].equals(AbstractCommand.METHOD_UNSUBSCRIBE) + || tokenizedCommand[0].equals(AbstractCommand.METHOD_UNSUBSCRIBED) + || tokenizedCommand[0].equals(AbstractCommand.METHOD_CALL) + || tokenizedCommand[0].equals(AbstractCommand.METHOD_RETURN) + || tokenizedCommand[0].equals(AbstractCommand.DIRECTIVE_SESSION) || tokenizedCommand[0] + .equals(AbstractCommand.DIRECTIVE_ERROR))) { + this.method = tokenizedCommand[0]; + } else { + throw new CommandParseException("unknown event"); + } + + } + + /** + * A bare-bones command + * + * @param method + */ + public AbstractCommand(String method) { + this.method = method; + } + + /** + * Method toCommandString. + * @return String + */ + public abstract String toCommandString(); + + /** + * Method toCommandString. + * @return String + */ + public abstract String getPayload(); + + /** + * Method parseJSONObject. + * @param payload String + * @return JSONObject + */ + protected JSONObject parseJSONObject(String payload) { + try { + return new JSONObject(payload); + } catch (JSONException je) { + Utilities.log("could not parse json"); + Utilities.log(payload); + return null; + } + } + + /** + * Method getMethod. + * @return String + */ + public String getMethod() { + return method; + } + + /** + * Method setMethod. + * @param method String + */ + public void setMethod(String method) { + this.method = method; + } + + /** + * Method getType. + * @return String + */ + public String getType() { + return type; + } + + /** + * Method setType. + * @param type String + */ + public void setType(String type) { + this.type = type; + } + + /** + * Method getSubject. + * @return String + */ + public String getSubject() { + return subject; + } + + /** + * Method setSubject. + * @param subject String + */ + public void setSubject(String subject) { + this.subject = subject; + } + + /** + * Method writeObject. + * @param out ObjectOutputStream + * @throws IOException + */ + protected void writeObject(ObjectOutputStream out) throws IOException { + // perform the default serialization for all non-transient, non-static + // fields + out.defaultWriteObject(); + } + + /** + * Method readObject. + * @param in ObjectInputStream + * @throws IOException + * @throws ClassNotFoundException + */ + protected void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + // always perform the default de-serialization first + in.defaultReadObject(); + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/AbstractInputCommand.java b/src/com/yahoo/connectedtv/ycommand/AbstractInputCommand.java new file mode 100644 index 0000000..4f9e3c6 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/AbstractInputCommand.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Abstract class that all Input protocol commands inherit from + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +@SuppressWarnings("serial") +public abstract class AbstractInputCommand extends AbstractCommand { + /** + * Constructor for AbstractInputCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public AbstractInputCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + if (tokenizedCommand.length > 2 && tokenizedCommand[1].equals(AbstractCommand.TYPE_INPUT)) { + this.type = AbstractCommand.TYPE_INPUT; + } else { + throw new CommandParseException("invalid event"); + } + } + + /** + * Constructor for AbstractInputCommand. + */ + public AbstractInputCommand() { + super("PUBLISH"); + this.type = AbstractCommand.TYPE_INPUT; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/AbstractMethodCommand.java b/src/com/yahoo/connectedtv/ycommand/AbstractMethodCommand.java new file mode 100644 index 0000000..cf333ef --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/AbstractMethodCommand.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Abstract class that all Method Call protocol commands inherit from + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +@SuppressWarnings("serial") +public abstract class AbstractMethodCommand extends AbstractCommand { + /** + * Field callId. + */ + protected String callId; + /** + * Field widgetId. + */ + protected String widgetId; + + /** + * Constructor for AbstractMethodCommand. + * + * @param method + * String + * @param widgetId + * String + * @param category + * String + * @param callId + * String + */ + public AbstractMethodCommand(String method, String widgetId, String category, String callId) { + super(method); + assert (method.equals(AbstractCommand.METHOD_CALL) || method.equals(AbstractCommand.METHOD_RETURN)); + this.widgetId = widgetId; + this.subject = category; + this.callId = callId; + } + + /** + * Constructor for AbstractMethodCommand. + * + * @param tokenizedCommand + * String[] + * @throws CommandParseException + */ + public AbstractMethodCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + this.subject = tokenizedCommand[1]; + this.widgetId = tokenizedCommand[2]; + + this.callId = tokenizedCommand[3]; + } + + /** + * Method toCommandString. + * + * @param payload + * String + * @return String + */ + public String toCommandString(String payload) { + StringBuffer sb = new StringBuffer(); + sb.append(this.method); + sb.append('|'); + sb.append(this.subject); + sb.append('|'); + sb.append(this.widgetId == null ? "null" : this.widgetId); + sb.append('|'); + sb.append((payload == null || payload.trim().equals("")) ? "null" : payload); + sb.append('|'); + sb.append(this.callId); + sb.append("|END"); + + return sb.toString(); + } + + /** + * Method getCallId. + * + * @return String + */ + public String getCallId() { + return callId; + } + + /** + * Method setCallId. + * + * @param callId + * String + */ + public void setCallId(String callId) { + this.callId = callId; + } + + /** + * Method getWidgetID. + * + * @return String + */ + public String getWidgetID() { + return widgetId; + } + + /** + * Method setWidgetID. + * + * @param widgetId + * String + */ + public void setWidgetID(String widgetId) { + this.widgetId = widgetId; + } + + /** + * Method getName. + * + * @return String + */ + public String getName() { + return subject; + } + + /** + * Method setName. + * + * @param name + * String + */ + public void setName(String name) { + this.subject = name; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/AbstractServiceCommand.java b/src/com/yahoo/connectedtv/ycommand/AbstractServiceCommand.java new file mode 100644 index 0000000..203aa40 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/AbstractServiceCommand.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Abstract class that all Service protocol commands inherit from + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +@SuppressWarnings("serial") +public abstract class AbstractServiceCommand extends AbstractCommand { + /** + * Constructor for AbstractServiceCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public AbstractServiceCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + if (tokenizedCommand.length > 2 && tokenizedCommand[1].equals(AbstractCommand.TYPE_SERVICE)) { + this.type = AbstractCommand.TYPE_SERVICE; + } else { + throw new CommandParseException("invalid event"); + } + } + + /** + * Constructor for AbstractServiceCommand. + */ + public AbstractServiceCommand() { + super("PUBLISH"); + this.type = AbstractCommand.TYPE_SERVICE; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/AbstractSessionCommand.java b/src/com/yahoo/connectedtv/ycommand/AbstractSessionCommand.java new file mode 100644 index 0000000..e00abf6 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/AbstractSessionCommand.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Abstract class that all Session protocol commands inherit from + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +@SuppressWarnings("serial") +public abstract class AbstractSessionCommand extends AbstractCommand { + /** + * Constructor for AbstractSessionCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public AbstractSessionCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + if (tokenizedCommand.length > 2 && tokenizedCommand[0].equals(AbstractCommand.DIRECTIVE_SESSION)) { + this.type = AbstractCommand.DIRECTIVE_SESSION; + } else { + throw new CommandParseException("invalid event"); + } + } + + /** + * Constructor for AbstractSessionCommand. + * @param method String + */ + public AbstractSessionCommand(String method) { + super(method); + this.type = AbstractCommand.DIRECTIVE_SESSION; + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/AuthSessionCommand.java b/src/com/yahoo/connectedtv/ycommand/AuthSessionCommand.java new file mode 100644 index 0000000..7390710 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/AuthSessionCommand.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.logging.Level; + +/** + * Wraps an protocol authorize session command + *

+ * Example Command: + * + *

+ * {@literal
+ * SESSION|AUTH|a44520388013d49fcdb16a6b7129bffb4e54ce99|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class AuthSessionCommand extends AbstractSessionCommand { + /** + * Field serialVersionUID. + * (value is -8967152467731112563) + */ + private static final long serialVersionUID = -8967152467731112563L; + /** + * Field userCode. + */ + private String userCode; + /** + * Field cert. + */ + private String cert; + + /** + * @param code + * code the user has inputed on screen + + * @param certificate String + */ + public AuthSessionCommand(String code, String certificate) { + super("SESSION"); + this.userCode = code; + this.cert = certificate; + } + + /** + * Method generateSignature. + * @return String + */ + private String generateSignature() { + if (this.cert.equals("") || this.cert == null) { + Utilities.log(Level.SEVERE, "NO CERTIFICATE FOUND!"); + } + // generate a SHA1-HMAC signature + String sig = ""; + try { + SecretKeySpec key = new SecretKeySpec(this.userCode.getBytes("UTF-8"), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(key); + + sig = this.byteArrayToHex(mac.doFinal(this.cert.getBytes("UTF-8"))); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + return sig; + } + + /** + * Method byteArrayToHex. + * @param a byte[] + * @return String + */ + private String byteArrayToHex(byte[] a) { + int hn, ln, cx; + String hexDigitChars = "0123456789abcdef"; + StringBuffer buf = new StringBuffer(a.length * 2); + for (cx = 0; cx < a.length; cx++) { + hn = ((int) (a[cx]) & 0x00ff) / 16; + ln = ((int) (a[cx]) & 0x000f); + buf.append(hexDigitChars.charAt(hn)); + buf.append(hexDigitChars.charAt(ln)); + } + return buf.toString(); + } + + /** + * Method toCommandString. + * @return String + */ + @Override + public String toCommandString() { + return String.format("SESSION|AUTH|%s|END", this.generateSignature()); + } + + @Override + public String getPayload() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + /** + * Method getUserCode. + * @return String + */ + public String getUserCode() { + return userCode; + } + + /** + * Method setUserCode. + * @param userCode String + */ + public void setUserCode(String userCode) { + this.userCode = userCode; + } + + /** + * Method getSignature. + * @return String + */ + public String getSignature() { + return this.generateSignature(); + } + + /** + * Method getCert. + * @return String + */ + public String getCert() { + return cert; + } + + /** + * Method setCert. + * @param cert String + */ + public void setCert(String cert) { + this.cert = cert; + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/Base64.java b/src/com/yahoo/connectedtv/ycommand/Base64.java new file mode 100644 index 0000000..973400c --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/Base64.java @@ -0,0 +1,2202 @@ +package com.yahoo.connectedtv.ycommand; + +/** + *

+ * Encodes and decodes to and from Base64 notation. + *

+ *

+ * Homepage: http://iharder.net/base64. + *

+ * + *

+ * Example: + *

+ * + * String encoded = Base64.encode( myByteArray );
+ * byte[] myByteArray = Base64.decode( encoded ); + * + *

+ * The options parameter, which appears in a few places, is used to + * pass several pieces of information to the encoder. In the "higher level" + * methods such as encodeBytes( bytes, options ) the options parameter can be + * used to indicate such things as first gzipping the bytes before encoding + * them, not inserting linefeeds, and encoding using the URL-safe and Ordered + * dialects. + *

+ * + *

+ * Note, according to RFC3548, Section 2.1, + * implementations should not add line feeds unless explicitly told to do so. + * I've got Base64 set to this behavior now, although earlier versions broke + * lines by default. + *

+ * + *

+ * The constants defined in Base64 can be OR-ed together to combine options, so + * you might make a call like this: + *

+ * + * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); + *

+ * to compress the data before encoding it and then making the output have + * newline characters. + *

+ *

+ * Also... + *

+ * String encoded = Base64.encodeBytes( crazyString.getBytes() ); + * + * + * + *

+ * Change Log: + *

+ *
    + *
  • v2.3.7 - Fixed subtle bug when base 64 input stream contained the value + * 01111111, which is an invalid base 64 character but should not throw an + * ArrayIndexOutOfBoundsException either. Led to discovery of mishandling (or + * potential for better handling) of other bad input characters. You should now + * get an IOException if you try decoding something that has bad characters in + * it.
  • + *
  • v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded + * string ended in the last column; the buffer was not properly shrunk and + * contained an extra (null) byte that made it into the string.
  • + *
  • v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size + * was wrong for files of size 31, 34, and 37 bytes.
  • + *
  • v2.3.4 - Fixed bug when working with gzipped streams whereby flushing the + * Base64.OutputStream closed the Base64 encoding (by padding with equals signs) + * too soon. Also added an option to suppress the automatic decoding of gzipped + * streams. Also added experimental support for specifying a class loader when + * using the + * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} method. + *
  • + *
  • v2.3.3 - Changed default char encoding to US-ASCII which reduces the + * internal Java footprint with its CharEncoders and so forth. Fixed some + * javadocs that were inconsistent. Removed imports and specified things like + * java.io.IOException explicitly inline.
  • + *
  • v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how + * big the final encoded data will be so that the code doesn't have to create + * two output arrays: an oversized initial one and then a final, exact-sized + * one. Big win when using the {@link #encodeBytesToBytes(byte[])} family of + * methods (and not using the gzip options which uses a different mechanism with + * streams and stuff).
  • + *
  • v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and + * some similar helper methods to be more efficient with memory by not returning + * a String but just a byte array.
  • + *
  • v2.3 - This is not a drop-in replacement! This is two + * years of comments and bug fixes queued up and finally executed. Thanks to + * everyone who sent me stuff, and I'm sorry I wasn't able to distribute your + * fixes to everyone else. Much bad coding was cleaned up including throwing + * exceptions where necessary instead of returning null values or something + * similar. Here are some changes that may affect you: + *
      + *
    • Does not break lines, by default. This is to keep in compliance + * with RFC3548.
    • + *
    • Throws exceptions instead of returning null values. Because some + * operations (especially those that may permit the GZIP option) use IO streams, + * there is a possiblity of an java.io.IOException being thrown. After some + * discussion and thought, I've changed the behavior of the methods to throw + * java.io.IOExceptions rather than return null if ever there's an error. I + * think this is more appropriate, though it will require some changes to your + * code. Sorry, it should have been done this way to begin with.
    • + *
    • Removed all references to System.out, System.err, and the like. + * Shame on me. All I can say is sorry they were ever there.
    • + *
    • Throws NullPointerExceptions and IllegalArgumentExceptions as + * needed such as when passed arrays are null or offsets are invalid.
    • + *
    • Cleaned up as much javadoc as I could to avoid any javadoc warnings. This + * was especially annoying before for people who were thorough in their own + * projects and then had gobs of javadoc warnings on this file.
    • + *
    + *
  • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug when + * using very small files (~< 40 bytes).
  • + *
  • v2.2 - Added some helper methods for encoding/decoding directly from one + * file to the next. Also added a main() method to support command line + * encoding/decoding from one file to the next. Also added these Base64 + * dialects: + *
      + *
    1. The default is RFC3548 format.
    2. + *
    3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates + * URL and file name friendly format as described in Section 4 of RFC3548. + * http://www.faqs.org/rfcs/rfc3548.html
    4. + *
    5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates + * URL and file name friendly format that preserves lexical ordering as + * described in http://www.faqs.org/qa/rfcc-1940.html
    6. + *
    + * Special thanks to Jim Kellerman at http://www.powerset.com/ for contributing + * the new Base64 dialects.
  • + * + *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. + * Added some convenience methods for reading and writing to and from files.
  • + *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on + * systems with other encodings (like EBCDIC).
  • + *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.
  • + *
  • v2.0 - I got rid of methods that used booleans to set options. Now + * everything is more consolidated and cleaner. The code now detects when data + * that's being decoded is gzip-compressed and will decompress it automatically. + * Generally things are cleaner. You'll probably have to change some method + * calls that you were making to support the new options format (ints + * that you "OR" together).
  • + *
  • v1.5.1 - Fixed bug when decompressing and decoding to a byte[] using + * decode( String s, boolean gzipCompressed ). Added the ability to + * "suspend" encoding in the Output Stream so you can turn on and off the + * encoding if you need to embed base64 data in an otherwise "normal" stream + * (like an XML file).
  • + *
  • v1.5 - Output stream pases on flush() command but doesn't do anything + * itself. This helps when using GZIP streams. Added the ability to + * GZip-compress objects before encoding them.
  • + *
  • v1.4 - Added helper methods to read/write files.
  • + *
  • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
  • + *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input + * stream where last buffer being read, if not completely full, was not + * returned.
  • + *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the + * wrong time.
  • + *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • + *
+ * + *

+ * I am placing this code in the Public Domain. Do with it as you will. This + * software comes with no guarantees or warranties but with plenty of + * well-wishing instead! Please visit http://iharder.net/base64 periodically + * to check for updates or to contribute improvements. + *

+ * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.3.7 + */ +public class Base64 { + + /* ******** P U B L I C F I E L D S ******** */ + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding in first bit. Value is one. */ + public final static int ENCODE = 1; + + /** Specify decoding in first bit. Value is zero. */ + public final static int DECODE = 0; + + /** Specify that data should be gzip-compressed in second bit. Value is two. */ + public final static int GZIP = 2; + + /** + * Specify that gzipped data should not be automatically gunzipped. + */ + public final static int DONT_GUNZIP = 4; + + /** Do break lines when encoding. Value is 8. */ + public final static int DO_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as + * described in Section 4 of RFC3548: http://www.faqs.org/rfcs/rfc3548.html. It is important to note that + * data encoded this way is not officially valid Base64, or at the + * very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + /** + * Encode using the special "ordered" dialect of Base64 described here: http://www.faqs.org/qa/rfcc- + * 1940.html. + */ + public final static int ORDERED = 32; + + /* ******** P R I V A T E F I E L D S ******** */ + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte) '\n'; + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "US-ASCII"; + + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in + // encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in + // encoding + + /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + /* + * Host platform me be something funny like EBCDIC, so we hardcode these + * values. + */ + private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', + (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', + (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', + (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', + (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', + (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', + (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' }; + + /** + * Translates a Base64 value to either its 6-bit reconstruction value or a + * negative number indicating some other meaning. + **/ + private final static byte[] _STANDARD_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal + // 0 + // - + // 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - + // 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through + // 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' + // through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' + // through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' + // through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - + // 139 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - + // 152 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - + // 165 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - + // 178 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - + // 191 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - + // 204 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - + // 217 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - + // 230 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - + // 243 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 + }; + + /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of + * RFC3548: http://www.faqs.org + * /rfcs/rfc3548.html. Notice that the last two bytes become "hyphen" + * and "underscore" instead of "plus" and "slash." + */ + private final static byte[] _URL_SAFE_ALPHABET = { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', + (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', + (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', + (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', + (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', + (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', + (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_' }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private final static byte[] _URL_SAFE_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal + // 0 + // - + // 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - + // 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through + // 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' + // through 'Z' + -9, -9, -9, -9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' + // through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' + // through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - + // 139 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - + // 152 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - + // 165 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - + // 178 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - + // 191 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - + // 204 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - + // 217 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - + // 230 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - + // 243 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 + }; + + /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but someone requested it, and it + * is described here: http:// + * www.faqs.org/qa/rfcc-1940.html. + */ + private final static byte[] _ORDERED_ALPHABET = { (byte) '-', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', + (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', + (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) '_', (byte) 'a', (byte) 'b', (byte) 'c', + (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', + (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', + (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z' }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private final static byte[] _ORDERED_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal + // 0 + // - + // 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - + // 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' + // through 'M' + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' + // through 'Z' + -9, -9, -9, -9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' + // through 'm' + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' + // through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 + // - 139 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - + // 152 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - + // 165 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - + // 178 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - + // 191 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - + // 204 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - + // 217 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - + // 230 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - + // 243 + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 + }; + + /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the + * options specified. It's possible, though silly, to specify ORDERED + * and URLSAFE in which case one of them will be picked, though there + * is no guarantee as to which one will be picked. + */ + private final static byte[] getAlphabet(int options) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_ALPHABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_ALPHABET; + } else { + return _STANDARD_ALPHABET; + } + } // end getAlphabet + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on the + * options specified. It's possible, though silly, to specify ORDERED and + * URL_SAFE in which case one of them will be picked, though there is no + * guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet(int options) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_DECODABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_DECODABET; + } else { + return _STANDARD_DECODABET; + } + } // end getAlphabet + + /** Defeats instantiation. */ + private Base64() { + } + + /* ******** E N C O D I N G M E T H O D S ******** */ + + /** + * Encodes up to the first three bytes of array threeBytes and + * returns a four-byte array in Base64 notation. The actual number of + * significant bytes in your array is given by numSigBytes. The + * array threeBytes needs only be as big as + * numSigBytes. Code can reuse a byte array by passing a + * four-byte array as b4. + * + * @param b4 + * A reusable byte array to reduce array instantiation + * @param threeBytes + * the array to convert + * @param numSigBytes + * the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes, int options) { + encode3to4(threeBytes, 0, numSigBytes, b4, 0, options); + return b4; + } // end encode3to4 + + /** + *

+ * Encodes up to three bytes of the array source and writes the + * resulting four Base64 bytes to destination. The source and + * destination arrays can be manipulated anywhere along their length by + * specifying srcOffset and destOffset. This method + * does not check to make sure your arrays are large enough to accomodate + * srcOffset + 3 for the source array or + * destOffset + 4 for the destination array. The + * actual number of significant bytes in your array is given by + * numSigBytes. + *

+ *

+ * This is the lowest level of the encoding methods with all possible + * parameters. + *

+ * + * @param source + * the array to convert + * @param srcOffset + * the index where conversion begins + * @param numSigBytes + * the number of significant bytes in your array + * @param destination + * the array to hold the conversion + * @param destOffset + * the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset, + int options) { + + byte[] ALPHABET = getAlphabet(options); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an + // int. + int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; + return destination; + + case 2: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + case 1: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + /** + * Performs Base64 encoding on the raw ByteBuffer, writing it + * to the encoded ByteBuffer. This is an experimental feature. + * Currently it does not pass along any options (such as + * {@link #DO_BREAK_LINES} or {@link #GZIP}. + * + * @param raw + * input buffer + * @param encoded + * output buffer + * @since 2.3 + */ + public static void encode(java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded) { + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while (raw.hasRemaining()) { + int rem = Math.min(3, raw.remaining()); + raw.get(raw3, 0, rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS); + encoded.put(enc4); + } // end input remaining + } + + /** + * Performs Base64 encoding on the raw ByteBuffer, writing it + * to the encoded CharBuffer. This is an experimental feature. + * Currently it does not pass along any options (such as + * {@link #DO_BREAK_LINES} or {@link #GZIP}. + * + * @param raw + * input buffer + * @param encoded + * output buffer + * @since 2.3 + */ + public static void encode(java.nio.ByteBuffer raw, java.nio.CharBuffer encoded) { + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while (raw.hasRemaining()) { + int rem = Math.min(3, raw.remaining()); + raw.get(raw3, 0, rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS); + for (int i = 0; i < 4; i++) { + encoded.put((char) (enc4[i] & 0xFF)); + } + } // end input remaining + } + + /** + * Serializes an object and returns the Base64-encoded version of that + * serialized object. + * + *

+ * As of v 2.3, if the object cannot be serialized or there is another + * error, the method will throw an java.io.IOException. This is new to + * v2.3! In earlier versions, it just returned a null value, but in + * retrospect that's a pretty poor way to handle it. + *

+ * + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject + * The object to encode + * @return The Base64-encoded object + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if serializedObject is null + * @since 1.4 + */ + public static String encodeObject(java.io.Serializable serializableObject) throws java.io.IOException { + return encodeObject(serializableObject, NO_OPTIONS); + } // end encodeObject + + /** + * Serializes an object and returns the Base64-encoded version of that + * serialized object. + * + *

+ * As of v 2.3, if the object cannot be serialized or there is another + * error, the method will throw an java.io.IOException. This is new to + * v2.3! In earlier versions, it just returned a null value, but in + * retrospect that's a pretty poor way to handle it. + *

+ * + * The object is not GZip-compressed before being encoded. + *

+ * Example options: + * + *

+	 *   GZIP: gzip-compresses object before encoding it.
+	 *   DO_BREAK_LINES: break lines at 76 characters
+	 * 
+ *

+ * Example: encodeObject( myObj, Base64.GZIP ) or + *

+ * Example: + * encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * @param serializableObject + * The object to encode + * @param options + * Specified options + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException + * if there is an error + * @since 2.0 + */ + public static String encodeObject(java.io.Serializable serializableObject, int options) throws java.io.IOException { + + if (serializableObject == null) { + throw new NullPointerException("Cannot serialize a null object."); + } // end if: null + + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.util.zip.GZIPOutputStream gzos = null; + java.io.ObjectOutputStream oos = null; + + try { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream(baos, ENCODE | options); + if ((options & GZIP) != 0) { + // Gzip + gzos = new java.util.zip.GZIPOutputStream(b64os); + oos = new java.io.ObjectOutputStream(gzos); + } else { + // Not gzipped + oos = new java.io.ObjectOutputStream(b64os); + } + oos.writeObject(serializableObject); + } // end try + catch (java.io.IOException e) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try { + oos.close(); + } catch (Exception e) { + } + try { + gzos.close(); + } catch (Exception e) { + } + try { + b64os.close(); + } catch (Exception e) { + } + try { + baos.close(); + } catch (Exception e) { + } + } // end finally + + // Return value according to relevant encoding. + try { + return new String(baos.toByteArray(), PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + // Fall back to some Java default + return new String(baos.toByteArray()); + } // end catch + + } // end encode + + /** + * Encodes a byte array into Base64 notation. Does not GZip-compress data. + * + * @param source + * The data to convert + * @return The data in Base64-encoded form + * @throws NullPointerException + * if source array is null + * @since 1.4 + */ + public static String encodeBytes(byte[] source) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + /** + * Encodes a byte array into Base64 notation. + *

+ * Example options: + * + *

+	 *   GZIP: gzip-compresses object before encoding it.
+	 *   DO_BREAK_LINES: break lines at 76 characters
+	 *     Note: Technically, this makes your encoding non-compliant.
+	 * 
+ *

+ * Example: encodeBytes( myData, Base64.GZIP ) or + *

+ * Example: + * encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * + *

+ * As of v 2.3, if there is an error with the GZIP stream, the method will + * throw an java.io.IOException. This is new to v2.3! In earlier + * versions, it just returned a null value, but in retrospect that's a + * pretty poor way to handle it. + *

+ * + * + * @param source + * The data to convert + * @param options + * Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if source array is null + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int options) throws java.io.IOException { + return encodeBytes(source, 0, source.length, options); + } // end encodeBytes + + /** + * Encodes a byte array into Base64 notation. Does not GZip-compress data. + * + *

+ * As of v 2.3, if there is an error, the method will throw an + * java.io.IOException. This is new to v2.3! In earlier versions, it + * just returned a null value, but in retrospect that's a pretty poor way to + * handle it. + *

+ * + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 1.4 + */ + public static String encodeBytes(byte[] source, int off, int len) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes(source, off, len, NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + /** + * Encodes a byte array into Base64 notation. + *

+ * Example options: + * + *

+	 *   GZIP: gzip-compresses object before encoding it.
+	 *   DO_BREAK_LINES: break lines at 76 characters
+	 *     Note: Technically, this makes your encoding non-compliant.
+	 * 
+ *

+ * Example: encodeBytes( myData, Base64.GZIP ) or + *

+ * Example: + * encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * + *

+ * As of v 2.3, if there is an error with the GZIP stream, the method will + * throw an java.io.IOException. This is new to v2.3! In earlier + * versions, it just returned a null value, but in retrospect that's a + * pretty poor way to handle it. + *

+ * + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @param options + * Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int off, int len, int options) throws java.io.IOException { + byte[] encoded = encodeBytesToBytes(source, off, len, options); + + // Return value according to relevant encoding. + try { + return new String(encoded, PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + return new String(encoded); + } // end catch + + } // end encodeBytes + + /** + * Similar to {@link #encodeBytes(byte[])} but returns a byte array instead + * of instantiating a String. This is more efficient if you're working with + * I/O streams and have large data sets to encode. + * + * + * @param source + * The data to convert + * @return The Base64-encoded data as a byte[] (of ASCII characters) + * @throws NullPointerException + * if source array is null + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes(byte[] source) { + byte[] encoded = null; + try { + encoded = encodeBytesToBytes(source, 0, source.length, Base64.NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); + } + return encoded; + } + + /** + * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns a byte + * array instead of instantiating a String. This is more efficient if you're + * working with I/O streams and have large data sets to encode. + * + * + * @param source + * The data to convert + * @param off + * Offset in array where conversion should begin + * @param len + * Length of data to convert + * @param options + * Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if source array is null + * @throws IllegalArgumentException + * if source array, offset, or length are invalid + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException { + + if (source == null) { + throw new NullPointerException("Cannot serialize a null array."); + } // end if: null + + if (off < 0) { + throw new IllegalArgumentException("Cannot have negative offset: " + off); + } // end if: off < 0 + + if (len < 0) { + throw new IllegalArgumentException("Cannot have length offset: " + len); + } // end if: len < 0 + + if (off + len > source.length) { + throw new IllegalArgumentException(String.format( + "Cannot have offset of %d and length of %d with array of length %d", off, len, source.length)); + } // end if: off < 0 + + // Compress? + if ((options & GZIP) != 0) { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + try { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream(baos, ENCODE | options); + gzos = new java.util.zip.GZIPOutputStream(b64os); + + gzos.write(source, off, len); + gzos.close(); + } // end try + catch (java.io.IOException e) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try { + gzos.close(); + } catch (Exception e) { + } + try { + b64os.close(); + } catch (Exception e) { + } + try { + baos.close(); + } catch (Exception e) { + } + } // end finally + + return baos.toByteArray(); + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else { + boolean breakLines = (options & DO_BREAK_LINES) != 0; + + // int len43 = len * 4 / 3; + // byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + // Try to determine more precisely how big the array needs to be. + // If we get it right, we don't have to do an array copy, and + // we save a bunch of memory. + int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed + // for actual + // encoding + if (breakLines) { + encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline + // characters + } + byte[] outBuff = new byte[encLen]; + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for (; d < len2; d += 3, e += 4) { + encode3to4(source, d + off, 3, outBuff, e, options); + + lineLength += 4; + if (breakLines && lineLength >= MAX_LINE_LENGTH) { + outBuff[e + 4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e, options); + e += 4; + } // end if: some padding needed + + // Only resize array if we didn't guess it right. + if (e <= outBuff.length - 1) { + // If breaking lines and the last byte falls right at + // the line length (76 bytes per line), there will be + // one extra byte, and the array will need to be resized. + // Not too bad of an estimate on array size, I'd say. + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff, 0, finalOut, 0, e); + // System.err.println("Having to resize array from " + + // outBuff.length + " to " + e ); + return finalOut; + } else { + // System.err.println("No need to resize array."); + return outBuff; + } + + } // end else: don't compress + + } // end encodeBytesToBytes + + /* ******** D E C O D I N G M E T H O D S ******** */ + + /** + * Decodes four bytes from array source and writes the resulting + * bytes (up to three of them) to destination. The source and + * destination arrays can be manipulated anywhere along their length by + * specifying srcOffset and destOffset. This method + * does not check to make sure your arrays are large enough to accomodate + * srcOffset + 4 for the source array or + * destOffset + 3 for the destination array. This + * method returns the actual number of bytes that were converted from the + * Base64 encoding. + *

+ * This is the lowest level of the decoding methods with all possible + * parameters. + *

+ * + * + * @param source + * the array to convert + * @param srcOffset + * the index where conversion begins + * @param destination + * the array to hold the conversion + * @param destOffset + * the index where output will be put + * @param options + * alphabet type is pulled from this (standard, url-safe, + * ordered) + * @return the number of decoded bytes converted + * @throws NullPointerException + * if source or destination arrays are null + * @throws IllegalArgumentException + * if srcOffset or destOffset are invalid or there is not enough + * room in the array. + * @since 1.3 + */ + private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset, int options) { + + // Lots of error checking and exception throwing + if (source == null) { + throw new NullPointerException("Source array was null."); + } // end if + if (destination == null) { + throw new NullPointerException("Destination array was null."); + } // end if + if (srcOffset < 0 || srcOffset + 3 >= source.length) { + throw new IllegalArgumentException(String.format( + "Source array with length %d cannot have offset of %d and still process four bytes.", + source.length, srcOffset)); + } // end if + if (destOffset < 0 || destOffset + 2 >= destination.length) { + throw new IllegalArgumentException(String.format( + "Destination array with length %d cannot have offset of %d and still store three bytes.", + destination.length, destOffset)); + } // end if + + byte[] DECODABET = getDecodabet(options); + + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 + // ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); + + destination[destOffset] = (byte) (outBuff >>> 16); + return 1; + } + + // Example: DkL= + else if (source[srcOffset + 3] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 + // ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); + + destination[destOffset] = (byte) (outBuff >>> 16); + destination[destOffset + 1] = (byte) (outBuff >>> 8); + return 2; + } + + // Example: DkLE + else { + // Two ways to do the same thing. Don't know which way I like best. + // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 + // ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) | ((DECODABET[source[srcOffset + 3]] & 0xFF)); + + destination[destOffset] = (byte) (outBuff >> 16); + destination[destOffset + 1] = (byte) (outBuff >> 8); + destination[destOffset + 2] = (byte) (outBuff); + + return 3; + } + } // end decodeToBytes + + /** + * Low-level access to decoding ASCII characters in the form of a byte + * array. Ignores GUNZIP option, if it's set. This is not + * generally a recommended method, although it is used internally as part of + * the decoding process. Special case: if len = 0, an empty array is + * returned. Still, if you need more speed and reduced memory footprint (and + * aren't gzipping), consider this method. + * + * @param source + * The Base64 encoded data + * @return decoded data + * @since 2.3.1 + */ + public static byte[] decode(byte[] source) throws java.io.IOException { + byte[] decoded = null; + // try { + decoded = decode(source, 0, source.length, Base64.NO_OPTIONS); + // } catch( java.io.IOException ex ) { + // assert false : + // "IOExceptions only come from GZipping, which is turned off: " + + // ex.getMessage(); + // } + return decoded; + } + + /** + * Low-level access to decoding ASCII characters in the form of a byte + * array. Ignores GUNZIP option, if it's set. This is not + * generally a recommended method, although it is used internally as part of + * the decoding process. Special case: if len = 0, an empty array is + * returned. Still, if you need more speed and reduced memory footprint (and + * aren't gzipping), consider this method. + * + * @param source + * The Base64 encoded data + * @param off + * The offset of where to begin decoding + * @param len + * The length of characters to decode + * @param options + * Can specify options such as alphabet type to use + * @return decoded data + * @throws java.io.IOException + * If bogus characters exist in source data + * @since 1.3 + */ + public static byte[] decode(byte[] source, int off, int len, int options) throws java.io.IOException { + + // Lots of error checking and exception throwing + if (source == null) { + throw new NullPointerException("Cannot decode null source array."); + } // end if + if (off < 0 || off + len > source.length) { + throw new IllegalArgumentException(String.format( + "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, + len)); + } // end if + + if (len == 0) { + return new byte[0]; + } else if (len < 4) { + throw new IllegalArgumentException( + "Base64-encoded string must have at least four characters, but length specified was " + len); + } // end if + + byte[] DECODABET = getDecodabet(options); + + int len34 = len * 3 / 4; // Estimate on array size + byte[] outBuff = new byte[len34]; // Upper limit on size of output + int outBuffPosn = 0; // Keep track of where we're writing + + byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating + // white space + int b4Posn = 0; // Keep track of four byte input buffer + int i = 0; // Source array counter + byte sbiDecode = 0; // Special value from DECODABET + + for (i = off; i < off + len; i++) { // Loop through source + + sbiDecode = DECODABET[source[i] & 0xFF]; + + // White space, Equals sign, or legit Base64 character + // Note the values such as -5 and -9 in the + // DECODABETs at the top of the file. + if (sbiDecode >= WHITE_SPACE_ENC) { + if (sbiDecode >= EQUALS_SIGN_ENC) { + b4[b4Posn++] = source[i]; // Save non-whitespace + if (b4Posn > 3) { // Time to decode? + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if (source[i] == EQUALS_SIGN) { + break; + } // end if: equals sign + } // end if: quartet built + } // end if: equals sign or better + } // end if: white space, equals sign or better + else { + // There's a bad input character in the Base64 stream. + throw new java.io.IOException(String.format( + "Bad Base64 input character decimal %d in array position %d", ((int) source[i]) & 0xFF, i)); + } // end else: + } // each input character + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } // end decode + + /** + * Decodes data from Base64 notation, automatically detecting + * gzip-compressed data and decompressing it. + * + * @param s + * the string to decode + * @return the decoded data + * @throws java.io.IOException + * If there is a problem + * @since 1.4 + */ + public static byte[] decode(String s) throws java.io.IOException { + return decode(s, NO_OPTIONS); + } + + /** + * Decodes data from Base64 notation, automatically detecting + * gzip-compressed data and decompressing it. + * + * @param s + * the string to decode + * @param options + * encode options such as URL_SAFE + * @return the decoded data + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if s is null + * @since 1.4 + */ + public static byte[] decode(String s, int options) throws java.io.IOException { + + if (s == null) { + throw new NullPointerException("Input string was null."); + } // end if + + byte[] bytes; + try { + bytes = s.getBytes(PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uee) { + bytes = s.getBytes(); + } // end catch + // + + // Decode + bytes = decode(bytes, 0, bytes.length, options); + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + boolean dontGunzip = (options & DONT_GUNZIP) != 0; + if ((bytes != null) && (bytes.length >= 4) && (!dontGunzip)) { + + int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream(bytes); + gzis = new java.util.zip.GZIPInputStream(bais); + + while ((length = gzis.read(buffer)) >= 0) { + baos.write(buffer, 0, length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch (java.io.IOException e) { + e.printStackTrace(); + // Just return originally-decoded bytes + } // end catch + finally { + try { + baos.close(); + } catch (Exception e) { + } + try { + gzis.close(); + } catch (Exception e) { + } + try { + bais.close(); + } catch (Exception e) { + } + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + /** + * Attempts to decode Base64 data and deserialize a Java Object within. + * Returns null if there was an error. + * + * @param encodedObject + * The Base64 data to decode + * @return The decoded and deserialized object + * @throws NullPointerException + * if encodedObject is null + * @throws java.io.IOException + * if there is a general error + * @throws ClassNotFoundException + * if the decoded object is of a class that cannot be found by + * the JVM + * @since 1.5 + */ + public static Object decodeToObject(String encodedObject) throws java.io.IOException, + java.lang.ClassNotFoundException { + return decodeToObject(encodedObject, NO_OPTIONS, null); + } + + /** + * Attempts to decode Base64 data and deserialize a Java Object within. + * Returns null if there was an error. If loader is not + * null, it will be the class loader used when deserializing. + * + * @param encodedObject + * The Base64 data to decode + * @param options + * Various parameters related to decoding + * @param loader + * Optional class loader to use in deserializing classes. + * @return The decoded and deserialized object + * @throws NullPointerException + * if encodedObject is null + * @throws java.io.IOException + * if there is a general error + * @throws ClassNotFoundException + * if the decoded object is of a class that cannot be found by + * the JVM + * @since 2.3.4 + */ + public static Object decodeToObject(String encodedObject, int options, final ClassLoader loader) + throws java.io.IOException, java.lang.ClassNotFoundException { + + // Decode and gunzip if necessary + byte[] objBytes = decode(encodedObject, options); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try { + bais = new java.io.ByteArrayInputStream(objBytes); + + // If no custom class loader is provided, use Java's builtin OIS. + if (loader == null) { + ois = new java.io.ObjectInputStream(bais); + } // end if: no loader provided + + // Else make a customized object input stream that uses + // the provided class loader. + else { + ois = new java.io.ObjectInputStream(bais) { + @Override + public Class resolveClass(java.io.ObjectStreamClass streamClass) throws java.io.IOException, + ClassNotFoundException { + @SuppressWarnings("rawtypes") + Class c = Class.forName(streamClass.getName(), false, loader); + if (c == null) { + return super.resolveClass(streamClass); + } else { + return c; // Class loader knows of this class. + } // end else: not null + } // end resolveClass + }; // end ois + } // end else: no custom class loader + + obj = ois.readObject(); + } // end try + catch (java.io.IOException e) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + catch (java.lang.ClassNotFoundException e) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + finally { + try { + bais.close(); + } catch (Exception e) { + } + try { + ois.close(); + } catch (Exception e) { + } + } // end finally + + return obj; + } // end decodeObject + + /** + * Convenience method for encoding data to a file. + * + *

+ * As of v 2.3, if there is a error, the method will throw an + * java.io.IOException. This is new to v2.3! In earlier versions, it + * just returned false, but in retrospect that's a pretty poor way to handle + * it. + *

+ * + * @param dataToEncode + * byte array of data to encode in base64 form + * @param filename + * Filename for saving encoded data + * @throws java.io.IOException + * if there is an error + * @throws NullPointerException + * if dataToEncode is null + * @since 2.1 + */ + public static void encodeToFile(byte[] dataToEncode, String filename) throws java.io.IOException { + + if (dataToEncode == null) { + throw new NullPointerException("Data to encode was null."); + } // end iff + + Base64.OutputStream bos = null; + try { + bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE); + bos.write(dataToEncode); + } // end try + catch (java.io.IOException e) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try { + bos.close(); + } catch (Exception e) { + } + } // end finally + + } // end encodeToFile + + /** + * Convenience method for decoding data to a file. + * + *

+ * As of v 2.3, if there is a error, the method will throw an + * java.io.IOException. This is new to v2.3! In earlier versions, it + * just returned false, but in retrospect that's a pretty poor way to handle + * it. + *

+ * + * @param dataToDecode + * Base64-encoded data as a string + * @param filename + * Filename for saving decoded data + * @throws java.io.IOException + * if there is an error + * @since 2.1 + */ + public static void decodeToFile(String dataToDecode, String filename) throws java.io.IOException { + + Base64.OutputStream bos = null; + try { + bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE); + bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); + } // end try + catch (java.io.IOException e) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try { + bos.close(); + } catch (Exception e) { + } + } // end finally + + } // end decodeToFile + + /** + * Convenience method for reading a base64-encoded file and decoding it. + * + *

+ * As of v 2.3, if there is a error, the method will throw an + * java.io.IOException. This is new to v2.3! In earlier versions, it + * just returned false, but in retrospect that's a pretty poor way to handle + * it. + *

+ * + * @param filename + * Filename for reading encoded data + * @return decoded byte array + * @throws java.io.IOException + * if there is an error + * @since 2.1 + */ + public static byte[] decodeFromFile(String filename) throws java.io.IOException { + + byte[] decodedData = null; + Base64.InputStream bis = null; + try { + // Set up some useful variables + java.io.File file = new java.io.File(filename); + byte[] buffer = null; + int length = 0; + int numBytes = 0; + + // Check for size of file + if (file.length() > Integer.MAX_VALUE) { + throw new java.io.IOException("File is too big for this convenience method (" + file.length() + + " bytes)."); + } // end if: file too big for int index + buffer = new byte[(int) file.length()]; + + // Open a stream + bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), + Base64.DECODE); + + // Read until done + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { + length += numBytes; + } // end while + + // Save in a variable to return + decodedData = new byte[length]; + System.arraycopy(buffer, 0, decodedData, 0, length); + + } // end try + catch (java.io.IOException e) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try { + bis.close(); + } catch (Exception e) { + } + } // end finally + + return decodedData; + } // end decodeFromFile + + /** + * Convenience method for reading a binary file and base64-encoding it. + * + *

+ * As of v 2.3, if there is a error, the method will throw an + * java.io.IOException. This is new to v2.3! In earlier versions, it + * just returned false, but in retrospect that's a pretty poor way to handle + * it. + *

+ * + * @param filename + * Filename for reading binary data + * @return base64-encoded string + * @throws java.io.IOException + * if there is an error + * @since 2.1 + */ + public static String encodeFromFile(String filename) throws java.io.IOException { + + String encodedData = null; + Base64.InputStream bis = null; + try { + // Set up some useful variables + java.io.File file = new java.io.File(filename); + byte[] buffer = new byte[Math.max((int) (file.length() * 1.4 + 1), 40)]; // Need + // max() + // for + // math + // on + // small + // files + // (v2.2.1); + // Need + // +1 + // for + // a + // few + // corner + // cases + // (v2.3.5) + int length = 0; + int numBytes = 0; + + // Open a stream + bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), + Base64.ENCODE); + + // Read until done + while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { + length += numBytes; + } // end while + + // Save in a variable to return + encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING); + + } // end try + catch (java.io.IOException e) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try { + bis.close(); + } catch (Exception e) { + } + } // end finally + + return encodedData; + } // end encodeFromFile + + /** + * Reads infile and encodes it to outfile. + * + * @param infile + * Input file + * @param outfile + * Output file + * @throws java.io.IOException + * if there is an error + * @since 2.2 + */ + public static void encodeFileToFile(String infile, String outfile) throws java.io.IOException { + + String encoded = Base64.encodeFromFile(infile); + java.io.OutputStream out = null; + try { + out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile)); + out.write(encoded.getBytes("US-ASCII")); // Strict, 7-bit output. + } // end try + catch (java.io.IOException e) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { + out.close(); + } catch (Exception ex) { + } + } // end finally + } // end encodeFileToFile + + /** + * Reads infile and decodes it to outfile. + * + * @param infile + * Input file + * @param outfile + * Output file + * @throws java.io.IOException + * if there is an error + * @since 2.2 + */ + public static void decodeFileToFile(String infile, String outfile) throws java.io.IOException { + + byte[] decoded = Base64.decodeFromFile(infile); + java.io.OutputStream out = null; + try { + out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile)); + out.write(decoded); + } // end try + catch (java.io.IOException e) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { + out.close(); + } catch (Exception ex) { + } + } // end finally + } // end decodeFileToFile + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + /** + * A {@link Base64.InputStream} will read data from another + * java.io.InputStream, given in the constructor, and encode/decode + * to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream { + + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + private int options; // Record options used to create the stream. + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link Base64.InputStream} in DECODE mode. + * + * @param in + * the java.io.InputStream from which to read data. + * @since 1.3 + */ + public InputStream(java.io.InputStream in) { + this(in, DECODE); + } // end constructor + + /** + * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE + * mode. + *

+ * Valid options: + * + *

+		 *   ENCODE or DECODE: Encode or Decode as data is read.
+		 *   DO_BREAK_LINES: break lines at 76 characters
+		 *     (only meaningful when encoding)
+		 * 
+ *

+ * Example: new Base64.InputStream( in, Base64.DECODE ) + * + * + * @param in + * the java.io.InputStream from which to read data. + * @param options + * Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 2.0 + */ + public InputStream(java.io.InputStream in, int options) { + + super(in); + this.options = options; // Record for later + this.breakLines = (options & DO_BREAK_LINES) > 0; + this.encode = (options & ENCODE) > 0; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[bufferLength]; + this.position = -1; + this.lineLength = 0; + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Reads enough of the input stream to convert to/from Base64 and + * returns the next byte. + * + * @return next byte + * @since 1.3 + */ + @Override + public int read() throws java.io.IOException { + + // Do we need to get data? + if (position < 0) { + if (encode) { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for (int i = 0; i < 3; i++) { + int b = in.read(); + + // If end of stream, b is -1. + if (b >= 0) { + b3[i] = (byte) b; + numBinaryBytes++; + } else { + break; // out of for loop + } // end else: end of stream + + } // end for: each needed input byte + + if (numBinaryBytes > 0) { + encode3to4(b3, 0, numBinaryBytes, buffer, 0, options); + position = 0; + numSigBytes = 4; + } // end if: got data + else { + return -1; // Must be end of stream + } // end else + } // end if: encoding + + // Else decoding + else { + byte[] b4 = new byte[4]; + int i = 0; + for (i = 0; i < 4; i++) { + // Read four "meaningful" bytes: + int b = 0; + do { + b = in.read(); + } while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC); + + if (b < 0) { + break; // Reads a -1 if end of stream + } // end if: end of stream + + b4[i] = (byte) b; + } // end for: each needed input byte + + if (i == 4) { + numSigBytes = decode4to3(b4, 0, buffer, 0, options); + position = 0; + } // end if: got four characters + else if (i == 0) { + return -1; + } // end else if: also padded correctly + else { + // Must have broken out from above. + throw new java.io.IOException("Improperly padded Base64 input."); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if (position >= 0) { + // End of relevant data? + if ( /* !encode && */position >= numSigBytes) { + return -1; + } // end if: got data + + if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { + lineLength = 0; + return '\n'; + } // end if + else { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[position++]; + + if (position >= bufferLength) { + position = -1; + } // end if: end + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else { + throw new java.io.IOException("Error in Base64 code reading stream."); + } // end else + } // end read + + /** + * Calls {@link #read()} repeatedly until the end of stream is reached + * or len bytes are read. Returns number of bytes read into + * array or -1 if end of stream is encountered. + * + * @param dest + * array to hold values + * @param off + * offset for array + * @param len + * max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + @Override + public int read(byte[] dest, int off, int len) throws java.io.IOException { + int i; + int b; + for (i = 0; i < len; i++) { + b = read(); + + if (b >= 0) { + dest[off + i] = (byte) b; + } else if (i == 0) { + return -1; + } else { + break; // Out of 'for' loop + } // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + /** + * A {@link Base64.OutputStream} will write data to another + * java.io.OutputStream, given in the constructor, and + * encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream { + + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + private int options; // Record for later + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link Base64.OutputStream} in ENCODE mode. + * + * @param out + * the java.io.OutputStream to which data will be + * written. + * @since 1.3 + */ + public OutputStream(java.io.OutputStream out) { + this(out, ENCODE); + } // end constructor + + /** + * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE + * mode. + *

+ * Valid options: + * + *

+		 *   ENCODE or DECODE: Encode or Decode as data is read.
+		 *   DO_BREAK_LINES: don't break lines at 76 characters
+		 *     (only meaningful when encoding)
+		 * 
+ *

+ * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * + * @param out + * the java.io.OutputStream to which data will be + * written. + * @param options + * Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 1.3 + */ + public OutputStream(java.io.OutputStream out, int options) { + super(out); + this.breakLines = (options & DO_BREAK_LINES) != 0; + this.encode = (options & ENCODE) != 0; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[bufferLength]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + this.options = options; + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Writes the byte to the output stream after converting to/from Base64 + * notation. When encoding, bytes are buffered three at a time before + * the output stream actually gets a write() call. When decoding, bytes + * are buffered four at a time. + * + * @param theByte + * the byte to write + * @since 1.3 + */ + @Override + public void write(int theByte) throws java.io.IOException { + // Encoding suspended? + if (suspendEncoding) { + this.out.write(theByte); + return; + } // end if: supsended + + // Encode? + if (encode) { + buffer[position++] = (byte) theByte; + if (position >= bufferLength) { // Enough to encode. + + this.out.write(encode3to4(b4, buffer, bufferLength, options)); + + lineLength += 4; + if (breakLines && lineLength >= MAX_LINE_LENGTH) { + this.out.write(NEW_LINE); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else { + // Meaningful Base64 character? + if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) { + buffer[position++] = (byte) theByte; + if (position >= bufferLength) { // Enough to output. + + int len = Base64.decode4to3(buffer, 0, b4, 0, options); + out.write(b4, 0, len); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) { + throw new java.io.IOException("Invalid character in Base64 data."); + } // end else: not white space either + } // end else: decoding + } // end write + + /** + * Calls {@link #write(int)} repeatedly until len bytes are + * written. + * + * @param theBytes + * array from which to read bytes + * @param off + * offset for array + * @param len + * max number of bytes to read into array + * @since 1.3 + */ + @Override + public void write(byte[] theBytes, int off, int len) throws java.io.IOException { + // Encoding suspended? + if (suspendEncoding) { + this.out.write(theBytes, off, len); + return; + } // end if: supsended + + for (int i = 0; i < len; i++) { + write(theBytes[off + i]); + } // end for: each byte written + + } // end write + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer + * without closing the stream. + * + * @throws java.io.IOException + * if there's an error. + */ + public void flushBase64() throws java.io.IOException { + if (position > 0) { + if (encode) { + out.write(encode3to4(b4, buffer, position, options)); + position = 0; + } // end if: encoding + else { + throw new java.io.IOException("Base64 input not properly padded."); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + @Override + public void close() throws java.io.IOException { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + /** + * Suspends encoding of the stream. May be helpful if you need to embed + * a piece of base64-encoded data in a stream. + * + * @throws java.io.IOException + * if there's an error flushing + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + /** + * Resumes encoding of the stream. May be helpful if you need to embed a + * piece of base64-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() { + this.suspendEncoding = false; + } // end resumeEncoding + + } // end inner class OutputStream + +} // end class Base64 \ No newline at end of file diff --git a/src/com/yahoo/connectedtv/ycommand/CommandParseException.java b/src/com/yahoo/connectedtv/ycommand/CommandParseException.java new file mode 100644 index 0000000..a047639 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/CommandParseException.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Error Parsing Command + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class CommandParseException extends Exception { + /** + * Field reason. + */ + public String reason; + + /** + * Constructor for CommandParseException. + * @param string String + */ + public CommandParseException(String string) { + reason = string; + } + + /** + * + */ + private static final long serialVersionUID = -1705421136555865313L; +} diff --git a/src/com/yahoo/connectedtv/ycommand/CommandParser.java b/src/com/yahoo/connectedtv/ycommand/CommandParser.java new file mode 100644 index 0000000..026a52a --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/CommandParser.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Parses commands and converts them into sub-classes of Command + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class CommandParser { + /** + * Parses message string and returns the correct Command object + * @param command String + * @return subclassed object of Command + * @throws CommandParseException + */ + public static AbstractCommand parse(String command) throws CommandParseException { + // Utilities.log("PARSING: " + command); + String[] tokenizedCommand = command.split(CommandSchema.DELIMITER_REGEX); + AbstractCommand newCommand = null; + if (tokenizedCommand.length > 2) { + Class cClass = CommandSchema.getInstance().matchCommand(tokenizedCommand); + if (cClass != null) { + @SuppressWarnings("rawtypes") + Class[] argsClass = new Class[1]; + argsClass[0] = tokenizedCommand.getClass(); + try { + // Get the constructor of the class matching the + // arguments passed + Constructor con = cClass.getConstructor(argsClass); + Object[] args = new Object[1]; + args[0] = tokenizedCommand; + + newCommand = con.newInstance(args); + } catch (IllegalArgumentException e) { + throw new CommandParseException(e.getClass().getSimpleName() + ":" + e.getMessage()); + } catch (InstantiationException e) { + throw new CommandParseException(e.getClass().getSimpleName() + ":" + e.getMessage()); + } catch (IllegalAccessException e) { + throw new CommandParseException(e.getClass().getSimpleName() + ":" + e.getMessage()); + } catch (InvocationTargetException e) { + e.printStackTrace(); + throw new CommandParseException(e.getClass().getSimpleName() + ":" + e.getMessage()); + } catch (SecurityException e) { + throw new CommandParseException(e.getClass().getSimpleName() + ":" + e.getMessage()); + } catch (NoSuchMethodException e) { + throw new CommandParseException(e.getClass().getSimpleName() + ":" + e.getMessage()); + } + } + } + if (newCommand != null) { + Utilities.log("CREATED NEW: " + newCommand.getClass().getCanonicalName()); + } else { + throw new CommandParseException("UNKNWON COMMAND: " + command); + } + return newCommand; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/CommandRouter.java b/src/com/yahoo/connectedtv/ycommand/CommandRouter.java new file mode 100644 index 0000000..cd264b5 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/CommandRouter.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import java.io.IOException; +import java.util.*; + +/** + * Hub for all incoming and outgoing commands. + * + * @author jecortez + * @version $Revision: 1.0 $ + */ +public class CommandRouter implements IConnectionHandler { + /** + * Field subscribers. + */ + private Map, List> subscribers; + /** + * Field rmcSubscribers - Remote Method Call Subscribers + */ + private Map> rmcSubscribers; + /** + * Field conn. + */ + private Connection conn; + + /** + * Constructor for CommandRouter. + */ + public CommandRouter() { + this.subscribers = new HashMap, List>(); + this.rmcSubscribers = new HashMap>(); + } + + /** + * @param conn + */ + public void setConnection(Connection conn) { + this.conn = conn; + } + + /** + * Method onConnectionClosed. + * + * @param connection Connection + * @see com.yahoo.connectedtv.ycommand.IConnectionHandler#onConnectionClosed(Connection) + */ + public void onConnectionClosed(Connection connection) { + Utilities.log("Connection broken " + connection.getAddr().toString() + " ------------"); + Collection> commandSubscriberLists = subscribers.values(); + if (commandSubscriberLists != null) { + for (List csl : commandSubscriberLists) { + for (ICommandSubscriber cs : csl) { + cs.onConnectionLost(connection); + } + csl.clear(); + } + subscribers.clear(); + } + } + + /** + * Method onDataAvailable. + * + * @param command String + * @throws IOException + * @throws CommandParseException + * @see com.yahoo.connectedtv.ycommand.IConnectionHandler#onDataAvailable(String) + */ + public void onDataAvailable(String command) throws IOException, CommandParseException { + AbstractCommand cmd = CommandParser.parse(command); + if (cmd != null) { + List commandSubscriberList = subscribers.get(cmd.getClass()); + if (cmd instanceof AbstractMethodCommand) { + Utilities.log(this.generateRmcSubscriberKey(((AbstractMethodCommand) cmd).widgetId, cmd.subject, + ((AbstractMethodCommand) cmd).callId)); + commandSubscriberList = this.rmcSubscribers.get(this.generateRmcSubscriberKey( + ((AbstractMethodCommand) cmd).widgetId, cmd.subject, ((AbstractMethodCommand) cmd).callId)); + } else { + commandSubscriberList = subscribers.get(cmd.getClass()); + } + if (commandSubscriberList != null) { + for (ICommandSubscriber cs : commandSubscriberList) { + cs.onCommandReceived(cmd); + } + } else { + Utilities.log("No one cares about:" + cmd.getClass().getCanonicalName()); + } + } + } + + /** + * @param command + * @throws IOException + * @throws InterruptedException + */ + public void publishCommand(AbstractCommand command) throws InterruptedException { + conn.write(command.toCommandString()); + } + + /** + * @param rawCommand + * @throws IOException + */ + public void publishCommand(String rawCommand) throws InterruptedException { + conn.write(rawCommand); + } + + /** + * @param commandType + * @param params Object[] + * @throws IOException + */ + public void subscribeTo(Class commandType, Object... params) throws InterruptedException { + String command = CommandSchema.getInstance().getSubscribeString(commandType); + if (command != null && !command.equals("")) { + this.publishCommand(String.format(command, params)); + } else { + Utilities.log("No command to subscribe to:" + commandType.getCanonicalName()); + } + } + + /** + * @param commandType + * @param params Object[] + * @throws IOException + */ + private void unsubscribeFrom(Class commandType, Object... params) throws InterruptedException { + String command = CommandSchema.getInstance().getUnsubscribeString(commandType); + if (command != null && !command.equals("")) { + this.publishCommand(String.format(command, params)); + } else { + Utilities.log("Don't know how to subscribe to:" + commandType.getCanonicalName()); + } + } + + /** + * @throws IOException + * @throws InterruptedException + */ + public void reestablishConnection() throws IOException, InterruptedException { + this.conn.establish(); + } + + /** + * @param widgetID + * @param subject + * @param callId + * @return + */ + public String generateRmcSubscriberKey(String widgetID, String subject, String callId) { + return widgetID + "-" + subject + "-" + callId; + } + + /** + * @param subscriber + */ + public void registerRMCCommandSubscriber(String widgetID, String subject, String callId, ICommandSubscriber subscriber) { + List commandSubscriberList = this.rmcSubscribers.get(this.generateRmcSubscriberKey( + widgetID, subject, callId)); + if (commandSubscriberList == null) { + commandSubscriberList = new LinkedList(); + Utilities.log(this.generateRmcSubscriberKey(widgetID, subject, callId)); + this.rmcSubscribers.put(this.generateRmcSubscriberKey(widgetID, subject, callId), commandSubscriberList); + } + if (!commandSubscriberList.contains(subscriber)) { + commandSubscriberList.add(subscriber); + } else { + Utilities.log("Preventing subscriber from subscribing twice to an RMC"); + } + + } + + //allow unregistering with a key as well + public void unregisterRMCCommandSubscriber(String key, ICommandSubscriber subscriber) { + List commandSubscriberList = this.rmcSubscribers.get(key); + if (commandSubscriberList != null && commandSubscriberList.contains(subscriber)) { + commandSubscriberList.remove(subscriber); + + if(commandSubscriberList.isEmpty()){ + rmcSubscribers.remove(key); + } + } else { + Utilities.log("unsubscribing from rmc you are not subscribed to!"); + } + } + + public void unregisterCommandSubscriber(String widgetID, String subject, String callId, ICommandSubscriber subscriber) { + String key = this.generateRmcSubscriberKey(widgetID, subject, callId); + unregisterRMCCommandSubscriber(key, subscriber); + } + + /** + * @param subscriber + * @param commandType + */ + public void registerCommandSubscriber(ICommandSubscriber subscriber, Class commandType, Object... params) throws InterruptedException { + Utilities.log("Router, Registering :" + commandType.getCanonicalName()); + List commandSubscriberList = subscribers.get(commandType); + if (commandSubscriberList == null) { + commandSubscriberList = new LinkedList(); + subscribers.put(commandType, commandSubscriberList); + } + if (!commandSubscriberList.contains(subscriber)) { + commandSubscriberList.add(subscriber); + this.subscribeTo(commandType, params); + } else { + Utilities.log("Preventing subscriber from subscribing twice to an event"); + } + } + + public void unregisterCommandSubscriber(ICommandSubscriber subscriber, Class commandType, Object... params) throws InterruptedException { + Utilities.log("Router, Unregistering :" + commandType.getCanonicalName()); + List commandSubscriberList = subscribers.get(commandType); + if (commandSubscriberList != null && commandSubscriberList.contains(subscriber)) { + commandSubscriberList.remove(subscriber); + + if(commandSubscriberList.isEmpty()){ + subscribers.remove(commandType); + this.unsubscribeFrom(commandType, params); + } + } else { + Utilities.log("unsubscribing from event you are not subscribed to!"); + } + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/CommandSchema.java b/src/com/yahoo/connectedtv/ycommand/CommandSchema.java new file mode 100644 index 0000000..aa3b26d --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/CommandSchema.java @@ -0,0 +1,276 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Singleton, Defines Schema for commands. Also matches tokenized commands to + * target classes. Normally used by library only. + * + * @author jecortez + * @version $Revision: 1.0 $ + */ +public class CommandSchema { + /** + * Field instance. + */ + private static CommandSchema instance; + + /** + * Field DELIMITER. (value is ""|"") + */ + public final static String DELIMITER = "|"; + /** + * Field DELIMITER_ESCAPE. (value is ""PIPE"") + */ + public final static String DELIMITER_ESCAPE = "PIPE"; + /** + * Field DELIMITER_REGEX. (value is ""\\|"") + */ + public final static String DELIMITER_REGEX = "\\|"; + + /** + * Field schemas. + */ + private List schemas; + + /** + * Field genericSchemas. + * These schemas are the catch-all and are not tied to any particular API. These will be matched last + */ + private List genericSchemas; + + /** + * Field subscribeSchemas. + */ + private Map, String> subscribeSchemas; + /** + * Field unsubscribeSchemas. + */ + private Map, String> unsubscribeSchemas; + + /** + * Constructor for CommandSchema. + */ + private CommandSchema() { + schemas = new LinkedList(); + schemas.add(new SchemaItem(new String[]{"SESSION", "GRANTED"}, GrantedSessionCommand.class)); + schemas.add(new SchemaItem(new String[]{"SESSION", "STATUS"}, StatusSessionCommand.class)); + + schemas.add(new SchemaItem(new String[]{"ERROR"}, ErrorCommand.class)); + + schemas.add(new SchemaItem(new String[]{"SUBSCRIBED"}, SubscribedCommand.class)); + schemas.add(new SchemaItem(new String[]{"UNSUBSCRIBED"}, UnsubscribedCommand.class)); + + schemas.add(new SchemaItem(new String[]{"PUBLISH", "INPUT", "keyboard"}, KeyboardInputCommand.class)); + schemas.add(new SchemaItem(new String[]{"PUBLISH", "INPUT", "navigation"}, NavigationInputCommand.class)); + schemas.add(new SchemaItem(new String[]{"PUBLISH", "INPUT", "mediacontrol"}, MediaControlInputCommand.class)); + + schemas.add(new SchemaItem(new String[]{"PUBLISH", "SERVICE", "widgetlist"}, WidgetListServiceCommand.class)); + schemas.add(new SchemaItem(new String[]{"PUBLISH", "SERVICE", "medialaunch"}, MediaLaunchServiceCommand.class)); + + schemas.add(new SchemaItem(new String[]{"CALL", "widgetLaunch"}, WidgetLaunchMethodCommand.class)); + schemas.add(new SchemaItem(new String[]{"RETURN", "widgetLaunch"}, WidgetLaunchMethodCommand.class)); + + // these are triggered last! + genericSchemas = new LinkedList(); + genericSchemas.add(new SchemaItem(new String[]{"PUBLISH", "WIDGET"}, WidgetCommand.class)); + genericSchemas.add(new SchemaItem(new String[]{"CALL"}, WidgetMethodCommand.class)); + genericSchemas.add(new SchemaItem(new String[]{"RETURN"}, WidgetMethodCommand.class)); + + subscribeSchemas = new HashMap, String>(); + subscribeSchemas.put(KeyboardInputCommand.class, "SUBSCRIBE|INPUT|keyboard|END"); + subscribeSchemas.put(NavigationInputCommand.class, "SUBSCRIBE|INPUT|navigation|END"); + subscribeSchemas.put(MediaControlInputCommand.class, "SUBSCRIBE|INPUT|mediacontrol|END"); + subscribeSchemas.put(WidgetListServiceCommand.class, "SUBSCRIBE|SERVICE|widgetlist|END"); + subscribeSchemas.put(MediaLaunchServiceCommand.class, "SUBSCRIBE|SERVICE|medialaunch|END"); + + subscribeSchemas.put(WidgetCommand.class, "SUBSCRIBE|WIDGET|%s|END"); + + unsubscribeSchemas = new HashMap, String>(); + unsubscribeSchemas.put(KeyboardInputCommand.class, "UNSUBSCRIBE|INPUT|keyboard|END"); + unsubscribeSchemas.put(NavigationInputCommand.class, "UNSUBSCRIBE|INPUT|navigation|END"); + unsubscribeSchemas.put(MediaControlInputCommand.class, "UNSUBSCRIBE|INPUT|mediacontrol|END"); + unsubscribeSchemas.put(WidgetListServiceCommand.class, "UNSUBSCRIBE|SERVICE|widgetlist|END"); + unsubscribeSchemas.put(MediaLaunchServiceCommand.class, "UNSUBSCRIBE|SERVICE|medialaunch|END"); + + unsubscribeSchemas.put(WidgetCommand.class, "UNSUBSCRIBE|WIDGET|%s|END"); + } + + /** + * Defines an new schema and target for example: + *

+ * {@code new SchemaItem(new String[] "PUBLISH", "INPUT", "navigation"}, + * NavigationInputCommand.class) } + * + * @author jecortez + */ + public static class SchemaItem { + /** + * Field schema. + */ + public String[] schema; + /** + * Field target. + */ + public Class target; + + /** + * Constructor for SchemaItem. + * + * @param schema String[] + * @param target Class + */ + public SchemaItem(String[] schema, Class target) { + this.schema = schema; + this.target = target; + } + + public String toString() { + StringBuffer sb = new StringBuffer("{schema:"); + sb.append(Utilities.stringArrayToString(this.schema)); + sb.append(", target:"); + sb.append(this.target.getCanonicalName()); + sb.append("}"); + return sb.toString(); + } + } + + + /** + * @return Singleton object + */ + public static CommandSchema getInstance() { + if (instance == null) { + instance = new CommandSchema(); + } + return instance; + } + + /** + * @param item Add custom item to the schema + */ + public synchronized void addSchema(SchemaItem item) { + schemas.add(item); + } + + /** + * Match a tokenized command to a target + * + * @param tokenizedCommand tokenized string command + * @return Class matched target + */ + public synchronized Class matchCommand(String[] tokenizedCommand) { + Class matchedCommand = matchSchemaList(tokenizedCommand, this.schemas); + if(matchedCommand == null){ + matchedCommand = matchSchemaList(tokenizedCommand, this.genericSchemas); + } + return matchedCommand; + } + + private Class matchSchemaList(String[] tokenizedCommand, List schemaList) { + Class matchedCommand = null; + for (SchemaItem item : schemaList) { + for (int i = 0; i <= item.schema.length; i++) { + if (i == item.schema.length) {// got to the end, so must be a match + matchedCommand = item.target; + break; + } else if (!item.schema[i].equals(tokenizedCommand[i])) { // doesn't match! + break; + } + } + if (matchedCommand != null) { + break; + } + } + return matchedCommand; + } + + /** + * Get the string to subscribe to a certain class type + * + * @param commandClass Command type to subscribe to + * @return subscribe string or null, String may have String.format place + * holders + */ + public String getSubscribeString(Class commandClass) { + return this.subscribeSchemas.get(commandClass); + } + + /** + * Add a new string to the unsubscribe string list + * + * @param commandClass + * @param unsubscribeString String may have String.format place holders + */ + public void addSubscribeString(Class commandClass, String unsubscribeString) { + this.subscribeSchemas.put(commandClass, unsubscribeString); + } + + /** + * Add a new string to the subscribe string list + * + * @param commandClass + * @param subscribeString String may have String.format place holders + */ + public void addUnsubscribeString(Class commandClass, String subscribeString) { + this.unsubscribeSchemas.put(commandClass, subscribeString); + } + + /** + * Get the string to unsubscribe to a certain class type + * + * @param commandClass Command type to unsubscribe from + * @return unsubscribe string or null, String may have String.format place + * holders + */ + public String getUnsubscribeString(Class commandClass) { + return this.unsubscribeSchemas.get(commandClass); + } + + /** + * Escape a command + * + * @param payload + * @return escaped payload + */ + public static String unescapeString(String payload) { + return payload.replaceAll(CommandSchema.DELIMITER_ESCAPE, CommandSchema.DELIMITER); + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/Connection.java b/src/com/yahoo/connectedtv/ycommand/Connection.java new file mode 100644 index 0000000..91c481b --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/Connection.java @@ -0,0 +1,562 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.charset.Charset; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +/** + * Wraps a SSL TCP Connection + * + * @author jecortez + * @version $Revision: 1.0 $ + */ +public class Connection { + /** + * Field PROTOCOL_VERSION. (value is 1) + */ + public static final int PROTOCOL_VERSION = 1; + /** + * Field DEFAULT_PORT. (value is 8099) + */ + public static final int DEFAULT_PORT = 8099; + + /** + * Field WRITER_THREAD_POLL_INTERVAL. seconds + */ + private static final long WRITER_THREAD_POLL_INTERVAL = 1; + + public static final String DNSSD_SERVICE_TYPE = "_yctvwidgets._tcp.local."; + + /** + * Field addr. + */ + protected InetSocketAddress addr; + /** + * Field socket. + */ + protected Socket socket; + /** + * Field reader. + */ + protected InputStreamReader reader; + /** + * Field writer. + */ + protected OutputStreamWriter writer; + + /** + * Field receiverThread. + */ + protected Thread receiverThread; + + /** + * Field writerThread. + */ + protected Thread writerThread; + + protected BlockingQueue outgoingCommands; + /** + * Field handler. + */ + protected IConnectionHandler handler; + + /** + * Field peerCertificate. + */ + protected String peerCertificate; + + /** + * Field isConnected. + */ + protected Boolean isConnected; + + /** + * Field isClosing. We are using this to distinguish between a close() call + * and a legit error coming from the socket + */ + protected Boolean isClosing; + private static final int DEFAULT_CONNECT_TIMEOUT = 30000; + + /** + * @param addr hostname or ip of tv + * @param port port to connect to (default is DEFAULT_PORT (8099)) + * @param handler Event handler, most likely a CommandRouter + * @throws UnknownHostException + */ + public Connection(String addr, int port, IConnectionHandler handler) throws UnknownHostException { + this(new InetSocketAddress(addr, port), handler); + } + + /** + * @param addr InetAddress object for the tv + * @param port port to connect to (default is DEFAULT_PORT (8099)) + * @param handler Event handler, most likely a CommandRouter + * @throws UnknownHostException + */ + public Connection(InetAddress addr, int port, IConnectionHandler handler) throws UnknownHostException { + this(new InetSocketAddress(addr, port), handler); + } + + /** + * @param addr contains both IP/hostname and port of the tv + * @param handler Event handler, most likely a CommandRouter + * @throws UnknownHostException + */ + public Connection(InetSocketAddress addr, IConnectionHandler handler) throws UnknownHostException { + this.addr = addr; + if (this.addr == null || this.addr.isUnresolved() || this.addr.getAddress() == null) { + throw new UnknownHostException("Cannot resolve " + this.addr); + } + this.handler = handler; + this.isConnected = false; + this.isClosing = true; + this.outgoingCommands = new LinkedBlockingQueue(); + } + + /** + * Method createReceiverThread. + * + * @param reader InputStreamReader + * @return Thread + */ + protected Thread createReceiverThread(InputStreamReader reader) { + /** + */ + class ReaderThread implements Runnable { + /** + * Field reader. + */ + InputStreamReader reader; + /** + * Field conn. + */ + Connection conn; + + /** + * Constructor for ReaderThread. + * + * @param reader + * InputStreamReader + * @param conn + * Connection + */ + public ReaderThread(InputStreamReader reader, Connection conn) { + this.reader = reader; + this.conn = conn; + } + + /** + * Method run. + * + * @see java.lang.Runnable#run() + */ + public void run() { + Utilities.log("Starting reader thread"); + while (this.conn.isActive()) { + try { + String command = readUntilEndTag(this.reader); + Utilities.log("RECEIVED:" + command); + + this.conn.handler.onDataAvailable(command); + } catch (IOException e) { + Utilities.log("Connection LOST!"); + e.printStackTrace(); + break; + } catch (CommandParseException e) { + Utilities.log("Ignoring Bad Command, reason:" + e.reason); + e.printStackTrace(); + } + } + this.conn.isConnected = false; + Utilities.log("Reader Thread closed"); + if (!Connection.this.isClosing) { + this.conn.close(); + } + } + } + ReaderThread rt = new ReaderThread(reader, this); + Thread thread = new Thread(null, rt, "SSLSocketReaderThread"); + thread.start(); + return thread; + } + + /** + * Method createWriterThread. + * + * @param writer OutputStreamWriter + * @return Thread + */ + protected Thread createWriterThread(OutputStreamWriter writer) { + /** + */ + class WriterThread implements Runnable { + OutputStreamWriter writer; + Connection conn; + + public WriterThread(OutputStreamWriter writer, Connection conn) { + this.writer = writer; + this.conn = conn; + } + + public void run() { + Utilities.log("Starting writer thread"); + while (true) { + try { + String command = Connection.this.outgoingCommands.poll(WRITER_THREAD_POLL_INTERVAL, TimeUnit.SECONDS); + + if (Connection.this.isClosing || this.conn == null || !this.conn.isActive()) { + break; + } + + if (command != null) { + try { + Utilities.log("WRITING:" + command); + this.writer.write(command); + this.writer.flush(); + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + + } + Utilities.log("Writer Thread closed"); + } + } + WriterThread rt = new WriterThread(writer, this); + Thread thread = new Thread(null, rt, "SSLSocketWriterThread"); + thread.start(); + return thread; + } + + /** + * Method readChar. + * + * @param reader InputStreamReader + * @return char + * @throws IOException + */ + protected char readChar(InputStreamReader reader) throws IOException { + int data = reader.read(); + //Utilities.log("READ:" + data + ":" + (char) data); + + if (data == -1) { + throw new IOException("Socket Disconnected, found EOF"); + } + + return (char) data; + + } + + /** + * Method readUntilEndTag. + * + * @param reader InputStreamReader + * @return String + * @throws IOException + */ + protected String readUntilEndTag(InputStreamReader reader) throws IOException { + StringBuffer sb = new StringBuffer(); + while (true) { + char level1Char = readChar(reader); + if (level1Char == '|') { + char level2Char = readChar(reader); + if (level2Char == 'E') { + char level3Char = readChar(reader); + if (level3Char == 'N') { + char level4Char = readChar(reader); + if (level4Char == 'D') { + break; + } else { + sb.append(level1Char); + sb.append(level2Char); + sb.append(level3Char); + sb.append(level4Char); + } + } else { + sb.append(level1Char); + sb.append(level2Char); + sb.append(level3Char); + } + } else { + sb.append(level1Char); + sb.append(level2Char); + } + } else { + sb.append(level1Char); + } + } + + // Utilities.log("Message size:" + sb.length()); + + return sb.toString(); + } + + /** + * Method createInsecureSSLConnection. + * + * @param addr InetSocketAddress + * @return Socket + * @throws IOException + */ + protected Socket createInsecureSSLConnection(InetSocketAddress addr) throws IOException { + SSLContext sc = null; + try { + sc = SSLContext.getInstance("TLS"); + + sc.init(null, new TrustManager[]{new EasyX509TrustManager(null)}, new java.security.SecureRandom()); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (KeyManagementException e) { + e.printStackTrace(); + } catch (KeyStoreException e) { + e.printStackTrace(); + } + + Socket socketConn = new Socket(); + socketConn.connect(addr, DEFAULT_CONNECT_TIMEOUT); + + return sc.getSocketFactory().createSocket(socketConn, addr.getHostName(), addr.getPort(), true); + } + + // One of the most irritating bugs yet, if you know of a better way, I would + // LOVE to hear it + + /** + * Method fixCertificate. + * + * @param cert byte[] + * @return String + */ + protected String fixCertificate(byte[] cert) { + String newCert = new String(Base64.encodeBytes(cert)); + + // first strip out all the new lines + newCert = newCert.replaceAll("\n", ""); + + // now lets insert the newlines at the correct spot + StringBuffer sb = new StringBuffer(); + sb.append("-----BEGIN CERTIFICATE-----"); + for (int i = 0; i < newCert.length(); i++) { + if (i % 64 == 0) { + sb.append('\n'); + } + sb.append(newCert.charAt(i)); + } + sb.append("\n-----END CERTIFICATE-----"); + return sb.toString(); + } + + /** + * Try to establish a connection to remote server. + * + * @throws InterruptedException + */ + public synchronized void establish() throws IOException, InterruptedException { + if (this.isActive()) { + Utilities.log("closing existing connection"); + this.close(false); // we are re-connecting + } + Utilities.log("Connecting to " + this.addr.getAddress() + ":" + this.addr.getPort()); + this.socket = createInsecureSSLConnection(this.addr); + if (this.socket != null) { + this.isConnected = true; + this.isClosing = false; + + Charset charset = Charset.forName("UTF-8"); + + this.reader = new InputStreamReader(socket.getInputStream(), charset); + this.writer = new OutputStreamWriter(this.socket.getOutputStream(), charset); + this.receiverThread = this.createReceiverThread(this.reader); + this.writerThread = this.createWriterThread(this.writer); + + Utilities.log("Connection established to " + this.addr.getAddress() + ":" + this.addr.getPort()); + + this.write(String.format("SESSION|CONFIG|%d|END", PROTOCOL_VERSION)); + + Certificate[] servercerts = ((SSLSocket) this.socket).getSession().getPeerCertificates(); + if (servercerts.length > 0) { + Utilities.log("Fixing certificates"); + try { + this.peerCertificate = this.fixCertificate(servercerts[0].getEncoded()); + } catch (CertificateEncodingException e) { + e.printStackTrace(); + this.close(); + throw new IOException("peer certificate encoding problem"); + } + Utilities.log("Certificates fixed and saved."); + } else { + Utilities.log(Level.SEVERE, "unable to get peer certificate"); + this.close(); + throw new IOException("unable to get peer certificate"); + } + + this.isConnected = this.socket.isConnected(); + + } + } + + /** + * Write a command to the socket connection + * + * @param command Command object to send + * @throws InterruptedException + */ + public void write(AbstractCommand command) throws InterruptedException { + this.write(command.toCommandString()); + } + + /** + * Write a string to the socket connection + * + * @param command Command string to send + * @throws InterruptedException + */ + public void write(String command) throws InterruptedException { + this.outgoingCommands.put(command); + } + + /** + * Close connection and notify all handlers + */ + public void close() { + this.close(true); + } + + /** + * Close connection + * + * @param notifyHandler Notify all connection listeners + */ + public synchronized void close(boolean notifyHandler) { + Utilities.log("Closing Connection to " + this.addr); + this.isConnected = false; + this.isClosing = true; + try { + if (this.socket != null) { + Utilities.log("Closing SOCKET"); + this.socket.close(); + } + if (this.reader != null) { + Utilities.log("Closing READER"); + this.reader.close(); + } + if (this.receiverThread != null && this.receiverThread.isAlive() + && !this.receiverThread.equals(Thread.currentThread())) { + Utilities.log("Closing READERTHREAD"); + this.receiverThread.join(); + } + this.reader = null; + this.socket = null; + this.receiverThread = null; + Utilities.log("Closing done."); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (IOException e) { + Utilities.log("Error disconnecting socket"); + e.printStackTrace(); + } + if (notifyHandler) { + this.handler.onConnectionClosed(this); + } + } + + /** + * Method isActive. + * + * @return boolean + */ + public boolean isActive() { + return this.socket != null && this.isConnected && this.socket.isConnected(); + } + + /** + * Method getAddr. + * + * @return InetSocketAddress + */ + public InetSocketAddress getAddr() { + return addr; + } + + /** + * Method getIP. + * + * @return InetAddress + */ + public InetAddress getIP() { + return addr.getAddress(); + } + + /** + * Method getPort. + * + * @return int + */ + public int getPort() { + return this.addr.getPort(); + } + + /** + * Method getPeerCertificate. + * + * @return String + */ + public String getPeerCertificate() { + return this.peerCertificate; + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/CreateSessionCommand.java b/src/com/yahoo/connectedtv/ycommand/CreateSessionCommand.java new file mode 100644 index 0000000..0d48dfc --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/CreateSessionCommand.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +/** + * Wraps an protocol create sessione command + *

+ * Example Command: + * + *

+ * {@literal
+ * SESSION|CREATE|a44520388013d49fcdb16a6b7129bffb4e54ce99|Jim's Awesome App|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class CreateSessionCommand extends AbstractSessionCommand { + /** + * Field serialVersionUID. (value is -7356217426030331170) + */ + private static final long serialVersionUID = -7356217426030331170L; + /** + * Field name. + */ + String name; + private String appId; + private String consumerKey; + private String secret; + + /** + * @param appId + * Application ID generated by Yahoo! + * @param consumerKey + * Consumer Key generated by Yahoo! + * @param secret + * Consumer Secret generated by Yahoo! + * @param name + * unique human-readable name of this app/user + */ + public CreateSessionCommand(String appId, String consumerKey, String secret, String name) { + super("SESSION"); + this.appId = appId; + this.consumerKey = consumerKey; + this.secret = secret; + this.name = name; + } + + /** + * Method toCommandString. + * + * @return String + */ + @Override + public String toCommandString() { + return String.format("SESSION|CREATE|%s|%s|END", this.getAppKey(), this.name); + } + + @Override + public String getPayload() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + /** + * Method getAppKey. + * + * @return String + */ + public String getAppKey() { + StringBuffer sb = new StringBuffer(); + try { + sb.append("app_id="); + sb.append(URLEncoder.encode(this.appId, "UTF-8")); + + sb.append("&consumer_key="); + sb.append(URLEncoder.encode(this.consumerKey, "UTF-8")); + + String secret = ""; + try { + SecretKeySpec key = new SecretKeySpec(this.secret.getBytes("UTF-8"), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(key); + + secret = this.byteArrayToHex(mac.doFinal(this.consumerKey.getBytes("UTF-8"))); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + sb.append("&secret="); + sb.append(URLEncoder.encode(secret, "UTF-8")); + + } catch (UnsupportedEncodingException e1) { + e1.printStackTrace(); + } + + return sb.toString(); + } + + /** + * Method byteArrayToHex. + * + * @param a + * byte[] + * @return String + */ + private String byteArrayToHex(byte[] a) { + int hn, ln, cx; + String hexDigitChars = "0123456789abcdef"; + StringBuffer buf = new StringBuffer(a.length * 2); + for (cx = 0; cx < a.length; cx++) { + hn = ((int) (a[cx]) & 0x00ff) / 16; + ln = ((int) (a[cx]) & 0x000f); + buf.append(hexDigitChars.charAt(hn)); + buf.append(hexDigitChars.charAt(ln)); + } + return buf.toString(); + } + + /** + * Method getName. + * + * @return String + */ + public String getName() { + return name; + } + + /** + * Method setName. + * + * @param name + * String + */ + public void setName(String name) { + this.name = name; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getConsumerKey() { + return consumerKey; + } + + public void setConsumerKey(String consumerKey) { + this.consumerKey = consumerKey; + } + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/EasyX509TrustManager.java b/src/com/yahoo/connectedtv/ycommand/EasyX509TrustManager.java new file mode 100644 index 0000000..2b59883 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/EasyX509TrustManager.java @@ -0,0 +1,82 @@ +package com.yahoo.connectedtv.ycommand; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * @author olamy + * @version $Id: EasyX509TrustManager.java 765355 2009-04-15 20:59:07Z evenisse + * $ + * @since 1.2.3 + */ +public class EasyX509TrustManager implements X509TrustManager { + + private X509TrustManager standardTrustManager = null; + + /** + * Constructor for EasyX509TrustManager. + */ + public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException { + super(); + TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + factory.init(keystore); + TrustManager[] trustmanagers = factory.getTrustManagers(); + if (trustmanagers.length == 0) { + throw new NoSuchAlgorithmException("no trust manager found"); + } + this.standardTrustManager = (X509TrustManager) trustmanagers[0]; + } + + /** + * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], + * String authType) + */ + public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException { + standardTrustManager.checkClientTrusted(certificates, authType); + } + + /** + * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], + * String authType) + */ + public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException { + if ((certificates != null) && (certificates.length == 1)) { + certificates[0].checkValidity(); + } else { + standardTrustManager.checkServerTrusted(certificates, authType); + } + } + + /** + * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() + */ + public X509Certificate[] getAcceptedIssuers() { + return this.standardTrustManager.getAcceptedIssuers(); + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/ErrorCommand.java b/src/com/yahoo/connectedtv/ycommand/ErrorCommand.java new file mode 100644 index 0000000..3972b59 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/ErrorCommand.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Wraps an protocol error command + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class ErrorCommand extends AbstractCommand { + /** + * Field serialVersionUID. + * (value is -7812335699456900252) + */ + private static final long serialVersionUID = -7812335699456900252L; + + /** + * Field CODE_INVALID_APP_KEY. + * (value is 1) + */ + public final static int CODE_INVALID_APP_KEY = 1; + /** + * Field CODE_INVALID_INSTANCE_KEY. + * (value is 2) + */ + public final static int CODE_INVALID_INSTANCE_KEY = 2; + /** + * Field CODE_INVALID_SIGNATURE. + * (value is 3) + */ + public final static int CODE_INVALID_SIGNATURE = 3; + /** + * Field CODE_BAD_MESSAGE. + * (value is 101) + */ + public final static int CODE_BAD_MESSAGE = 101; + + /** + * Field errorCode. + */ + private int errorCode; + /** + * Field errorDescription. + */ + private String errorDescription; + + /** + * Constructor for ErrorCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public ErrorCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + if (tokenizedCommand.length > 2 && tokenizedCommand[0].equals(AbstractCommand.DIRECTIVE_ERROR)) { + this.type = AbstractCommand.DIRECTIVE_ERROR; + this.errorCode = Integer.parseInt(tokenizedCommand[1]); + this.errorDescription = tokenizedCommand[2]; + } else { + throw new CommandParseException("invalid event"); + } + } + + /** + * Method getErrorCode. + * @return int + */ + public int getErrorCode() { + return errorCode; + } + + /** + * Method getErrorDescription. + * @return String + */ + public String getErrorDescription() { + return errorDescription; + } + + /** + * Method toCommandString. + * @return String + */ + public String toCommandString() { + String code = Integer.toString(this.errorCode); + if (this.errorCode < 100) { + code = "0" + code; + if (this.errorCode < 10) { + code = "0" + code; + } + } + return String.format("ERROR|%s|%s|END", code, this.errorDescription); + } + + @Override + public String getPayload() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/GrantedSessionCommand.java b/src/com/yahoo/connectedtv/ycommand/GrantedSessionCommand.java new file mode 100644 index 0000000..278aeb3 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/GrantedSessionCommand.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Wraps a Session granted protocol command + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class GrantedSessionCommand extends AbstractSessionCommand { + /** + * Field serialVersionUID. + * (value is 5428563604893800002) + */ + private static final long serialVersionUID = 5428563604893800002L; + /** + * Field instanceId. + */ + String instanceId; + + /** + * Constructor for GrantedSessionCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public GrantedSessionCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + if (tokenizedCommand.length > 2) { + this.instanceId = tokenizedCommand[2]; + } else { + throw new CommandParseException("No instanceID"); + } + } + + /** + * Get the unique ID assigned to the client device + * + + * @return unique id */ + public String getInstanceId() { + return instanceId; + } + + /** + * Method setInstanceId. + * @param instanceId String + */ + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + /** + * Method toCommandString. + * @return String + */ + public String toCommandString() { + return String.format("SESSION|GRANTED|%s|END", this.instanceId); + } + + @Override + public String getPayload() { + return instanceId; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/ICommandSubscriber.java b/src/com/yahoo/connectedtv/ycommand/ICommandSubscriber.java new file mode 100644 index 0000000..da4fc4e --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/ICommandSubscriber.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Interface for objects that want to receive commands + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public interface ICommandSubscriber { + /** + * Method onCommandReceived. + * @param command AbstractCommand + */ + public void onCommandReceived(AbstractCommand command); + + /** + * Method onConnectionLost. + * @param conn Connection + */ + public void onConnectionLost(Connection conn); +} diff --git a/src/com/yahoo/connectedtv/ycommand/IConnectionHandler.java b/src/com/yahoo/connectedtv/ycommand/IConnectionHandler.java new file mode 100644 index 0000000..2775223 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/IConnectionHandler.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import java.io.IOException; + +/** + * interface for classes that want to listen to a connection (Generally the + * Command Router) + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public interface IConnectionHandler { + /** + * When a connection is lost or closed + * + * @param connection + * the connection that was closed + */ + public void onConnectionClosed(Connection connection); + + /** + * Data is available on the connection. This really means that the previous + * message was consumed and someone shoul wait for more data. + * + * @param command + * The buffer to read from + + + * @throws IOException * @throws CommandParseException */ + public void onDataAvailable(String command) throws IOException, CommandParseException; +} diff --git a/src/com/yahoo/connectedtv/ycommand/IPCodeResolver.java b/src/com/yahoo/connectedtv/ycommand/IPCodeResolver.java new file mode 100644 index 0000000..bd3d0bc --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/IPCodeResolver.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Uses the Yahoo! backend service to fetch an IP and port. The user must go to + * the Profile widget, then system settings, then "Pair Device" to view the + * code. This code is then used to lookup the IP and port of the TV. + * + * @author jecortez + * @version $Revision: 1.0 $ + */ +public class IPCodeResolver { + /** + * Field DEFAULT_TIMEOUT. + * (value is 1000) + */ + public static final int DEFAULT_TIMEOUT = 1000; + /** + * Field BASE_URL. + */ + public static String BASE_URL = "http://bis.tv.widgets.yahoo.com/disc"; + + /** + * Field code. + */ + private String code; + + /** + * @param code + * Code the user has viewed on the TV screen + */ + public IPCodeResolver(String code) { + this.code = code; + } + + /** + * Method fetchIPText. + * @param url String + * @param timeout int + * @return String + */ + private String fetchIPText(String url, int timeout) { + String ipText = null; + try { + URL urlConnection = new URL(url); + + HttpURLConnection conn = (HttpURLConnection) urlConnection.openConnection(); + conn.setConnectTimeout(timeout); + conn.setRequestMethod("GET"); + + conn.connect(); + InputStream in = conn.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + StringBuffer ipTextBuffer = new StringBuffer(); + String line; + while ((line = reader.readLine()) != null) { + ipTextBuffer.append(line); + } + ipText = ipTextBuffer.toString(); + + conn.disconnect(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return ipText; + } + + /** + * Get the address of the TV, this does a synchronous HTTP fetch + * + + * @return Address of the TV */ + public InetSocketAddress getAddress() { + return this.getAddress(DEFAULT_TIMEOUT); + } + + /** + * Get the address of the TV, this does a synchronous HTTP fetch + * + * @param timeout + * in milliseconds for the HTTP fetch + + * @return Address of the TV */ + public InetSocketAddress getAddress(int timeout) { + String ipText = this.fetchIPText(BASE_URL + "/getIP.php?format=json&code=" + code, timeout); + if (ipText != null) { + String ip = null; + int port = -1; + try { + JSONObject response = new JSONObject(ipText); + ip = response.getString("ip"); + port = response.getInt("port"); + } catch (JSONException je) { + Utilities.log("could not parse json"); + Utilities.log(ipText); + } + + if (ip != null && port > 0) { + return new InetSocketAddress(ip, port); + } + } + return null; + } + + /** + * Method getCode. + * @return String + */ + public String getCode() { + return code; + } + + /** + * Method setCode. + * @param code String + */ + public void setCode(String code) { + this.code = code; + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/KeyboardInputCommand.java b/src/com/yahoo/connectedtv/ycommand/KeyboardInputCommand.java new file mode 100644 index 0000000..c1deda9 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/KeyboardInputCommand.java @@ -0,0 +1,473 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Wraps an protocol keyboard state change command + *

+ * Example Command: + * + *

+ * {@literal
+ * PUBLISH|INPUT|keyboard|{"totalValue": "foobar", "key": "o", "keyCode":false, "isKeyboardOnScreen": true, "cursorPosition": 2, "modifiers": {"control": false, "shift": false, "alt": false}, "layoutType": 1}|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class KeyboardInputCommand extends AbstractInputCommand { + /** + * + */ + private static final long serialVersionUID = 4574408368828955819L; + + /** + * Field LAYOUT_QUERTY. + * (value is 0) + */ + public final static int LAYOUT_QUERTY = 0; + /** + * Field LAYOUT_PIN. + * (value is 1) + */ + public final static int LAYOUT_PIN = 1; + + /** + * Field KEY_BACKSPACE. + * (value is 8) + */ + public final static int KEY_BACKSPACE = 8; + /** + * Field KEY_TAB. + * (value is 9) + */ + public final static int KEY_TAB = 9; + /** + * Field KEY_RETURN. + * (value is 13) + */ + public final static int KEY_RETURN = 13; + /** + * Field KEY_CAPS_LOCK. + * (value is 20) + */ + public final static int KEY_CAPS_LOCK = 20; + /** + * Field KEY_ESCAPE. + * (value is 27) + */ + public final static int KEY_ESCAPE = 27; + /** + * Field KEY_PAGE_UP. + * (value is 33) + */ + public final static int KEY_PAGE_UP = 33; + /** + * Field KEY_PAGE_DOWN. + * (value is 34) + */ + public final static int KEY_PAGE_DOWN = 34; + /** + * Field KEY_END. + * (value is 35) + */ + public final static int KEY_END = 35; + /** + * Field KEY_HOME. + * (value is 36) + */ + public final static int KEY_HOME = 36; + /** + * Field KEY_PRINTSCREEN. + * (value is 44) + */ + public final static int KEY_PRINTSCREEN = 44; + /** + * Field KEY_INSERT. + * (value is 45) + */ + public final static int KEY_INSERT = 45; + /** + * Field KEY_DELETE. + * (value is 46) + */ + public final static int KEY_DELETE = 46; + /** + * Field KEY_F10. + * (value is 121) + */ + public final static int KEY_F10 = 121; + /** + * Field KEY_NUM_LOCK. + * (value is 144) + */ + public final static int KEY_NUM_LOCK = 144; + /** + * Field KEY_SCROLL_LOCK. + * (value is 145) + */ + public final static int KEY_SCROLL_LOCK = 145; + + /** + * Field layoutType. + */ + private int layoutType = LAYOUT_QUERTY; + /** + * Field totalValue. + */ + private String totalValue = ""; + /** + * Field cursorPosition. + */ + private int cursorPosition = 0; + /** + * Field key. + */ + private String key = ""; + /** + * Field keyCode. + */ + private int keyCode = -1; + /** + * Field keyboardOnScreen. + */ + private boolean keyboardOnScreen = false; + /** + * Field shiftModifier. + */ + private boolean shiftModifier = false; + /** + * Field altModifier. + */ + private boolean altModifier = false; + /** + * Field controlModifier. + */ + private boolean controlModifier = false; + + /** + * Constructor for KeyboardInputCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public KeyboardInputCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + + if (tokenizedCommand.length > 2 && tokenizedCommand[2].equals("keyboard")) { + this.subject = "keyboard"; + if(tokenizedCommand[3] != null){ + this.jsonToMembers(this.parseJSONObject(tokenizedCommand[3])); + } else { + throw new CommandParseException("payload cannot be null"); + } + } else { + throw new CommandParseException("not keyboard event"); + } + } + + /** + * @param layoutType + * LAYOUT_QUERTY or LAYOUT_PIN + * @param totalValue + * total value of the text field + * @param cursorPosition + * current 0-based position of the cusor + * @param key + * last key hit, empty if not printable + * @param keyCode + * keycode of last key if key was not printable + * @param shiftModifier + * is Shift activated + * @param altModifier + * is alt activated + * @param controlModifier + * is control activated + */ + public KeyboardInputCommand(int layoutType, String totalValue, int cursorPosition, String key, int keyCode, + boolean shiftModifier, boolean altModifier, boolean controlModifier) { + super(); + this.subject = "keyboard"; + + this.layoutType = layoutType; + this.totalValue = totalValue; + this.cursorPosition = cursorPosition; + this.key = key; + this.keyCode = keyCode; + this.shiftModifier = shiftModifier; + this.altModifier = altModifier; + this.controlModifier = controlModifier; + } + + /** + * Method jsonToMembers. + * @param payload JSONObject + * @throws CommandParseException + */ + private void jsonToMembers(JSONObject payload) throws CommandParseException { + if (payload == null) { + throw new CommandParseException("no payload found"); + } + try { + if (payload.has("layoutType")) { + this.layoutType = payload.getInt("layoutType"); + } + + if (payload.has("totalValue")) { + this.totalValue = payload.getString("totalValue"); + } + + if (payload.has("cursorPosition")) { + try { + this.cursorPosition = payload.getInt("cursorPosition"); + } catch (JSONException je) { + System.out.println("Unable to parse cursor position"); + } + } + + if (payload.has("key")) { + this.key = payload.getString("key"); + } + + if (payload.has("keyCode")) { + this.keyCode = payload.getInt("keyCode"); + } + + if (payload.has("isKeyboardOnScreen")) { + this.keyboardOnScreen = payload.getBoolean("isKeyboardOnScreen"); + } + + if (payload.has("modifiers")) { + JSONObject modifiers = payload.getJSONObject("modifiers"); + this.altModifier = modifiers.getBoolean("alt"); + this.shiftModifier = modifiers.getBoolean("shift"); + this.controlModifier = modifiers.getBoolean("control"); + } + } catch (JSONException e) { + e.printStackTrace(); + throw new CommandParseException("Error parsing JSON"); + } + } + + /** + * Method membersToJSON. + * @return JSONObject + */ + private JSONObject membersToJSON() { + JSONObject retval = new JSONObject(); + + try { + retval.put("layoutType", this.layoutType); + retval.put("totalValue", this.totalValue); + retval.put("cursorPosition", this.cursorPosition); + retval.put("key", this.key); + retval.put("keyCode", this.keyCode); + + JSONObject modifiers = new JSONObject(); + modifiers.put("shift", this.shiftModifier); + modifiers.put("alt", this.altModifier); + modifiers.put("control", this.controlModifier); + retval.put("modifiers", modifiers); + } catch (JSONException e) { + Utilities.log("ERROR CREATING JSON MESSAGE"); + } + + return retval; + } + + /** + * Method toCommandString. + * @return String + */ + @Override + public String toCommandString() { + StringBuffer sb = new StringBuffer(); + sb.append(this.method); + sb.append("|INPUT|keyboard|"); + sb.append(this.membersToJSON()); + sb.append("|END"); + + return sb.toString(); + } + + @Override + public String getPayload() { + return this.membersToJSON().toString(); + } + + /** + + * @return LAYOUT_PIN or LAYOUT_QWERTY */ + public int getLayoutType() { + return layoutType; + } + + /** + * @param layoutType + * LAYOUT_PIN or LAYOUT_QWERTY + */ + public void setLayoutType(int layoutType) { + this.layoutType = layoutType; + } + + /** + + * @return value of the text field */ + public String getTotalValue() { + return totalValue; + } + + /** + * @param totalValue + * value of the text field + */ + public void setTotalValue(String totalValue) { + this.totalValue = totalValue; + } + + /** + + * @return 0-based position of the cursor */ + public int getCursorPosition() { + return cursorPosition; + } + + /** + * @param cursorPosition + * 0-based position of the cursor + */ + public void setCursorPosition(int cursorPosition) { + this.cursorPosition = cursorPosition; + } + + /** + + * @return Last character entered, empty string if not printable */ + public String getKey() { + return key; + } + + /** + * @param key + * Last character entered, empty string if not printable + */ + public void setKey(String key) { + this.key = key; + } + + /** + + * @return status of the shift modifier */ + public boolean isShiftModifier() { + return shiftModifier; + } + + /** + * @param shiftModifier + * status of the shift modifier + */ + public void setShiftModifier(boolean shiftModifier) { + this.shiftModifier = shiftModifier; + } + + /** + + * @return status of the alt modifier */ + public boolean isAltModifier() { + return altModifier; + } + + /** + * @param altModifier + * status of the alt modifier + */ + public void setAltModifier(boolean altModifier) { + this.altModifier = altModifier; + } + + /** + + * @return status of the control modifier */ + public boolean isControlModifier() { + return controlModifier; + } + + /** + * @param controlModifier + * status of the controls modifier + */ + public void setControlModifier(boolean controlModifier) { + this.controlModifier = controlModifier; + } + + /** + + * @return is keyboard currently on TV screen */ + public boolean isKeyboardOnScreen() { + return keyboardOnScreen; + } + + /** + * @param keyboardOnScreen + * is keyboard currently on TV screen + */ + public void setKeyboardOnScreen(boolean keyboardOnScreen) { + this.keyboardOnScreen = keyboardOnScreen; + } + + /** + + * @return if last key was not printable, the keyCode see KEY_* for + * constants */ + public int getKeyCode() { + return keyCode; + } + + /** + * @param keyCode + * if last key was not printable, the keyCode see KEY_* for + * constants + */ + public void setKeyCode(int keyCode) { + this.keyCode = keyCode; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/MediaControlInputCommand.java b/src/com/yahoo/connectedtv/ycommand/MediaControlInputCommand.java new file mode 100644 index 0000000..025f30e --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/MediaControlInputCommand.java @@ -0,0 +1,336 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Wraps an protocol media transport controls state change command + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class MediaControlInputCommand extends AbstractInputCommand { + /** + * Field serialVersionUID. + * (value is 4302545704172360159) + */ + private static final long serialVersionUID = 4302545704172360159L; + + /** + * Field STATE_INIT. + * (value is -1) + */ + public static final int STATE_INIT = -1; + /** + * Field STATE_PLAY. + * (value is 0) + */ + public static final int STATE_PLAY = 0; + /** + * Field STATE_PAUSE. + * (value is 1) + */ + public static final int STATE_PAUSE = 1; + /** + * Field STATE_FASTFORWARD. + * (value is 2) + */ + public static final int STATE_FASTFORWARD = 2; + /** + * Field STATE_FORWARD. + * (value is 2) + */ + public static final int STATE_FORWARD = 2; + /** + * Field STATE_FF. + * (value is 2) + */ + public static final int STATE_FF = 2; + /** + * Field STATE_REWIND. + * (value is 3) + */ + public static final int STATE_REWIND = 3; + /** + * Field STATE_STOP. + * (value is 4) + */ + public static final int STATE_STOP = 4; + /** + * Field STATE_BUFFERING. + * (value is 5) + */ + public static final int STATE_BUFFERING = 5; + /** + * Field STATE_BUFFEREMPTY. + * (value is 6) + */ + public static final int STATE_BUFFEREMPTY = 6; + /** + * Field STATE_INFOLOADED. + * (value is 7) + */ + public static final int STATE_INFOLOADED = 7; + /** + * Field STATE_EOF. + * (value is 8) + */ + public static final int STATE_EOF = 8; + /** + * Field STATE_UNKNOWN. + * (value is 9) + */ + public static final int STATE_UNKNOWN = 9; + /** + * Field STATE_ERROR. + * (value is 10) + */ + public static final int STATE_ERROR = 10; + + /** + * Field state. + */ + private int state = -1; + /** + * Field timeindex. + */ + private double timeindex; + /** + * Field duration. + */ + private double duration; + /** + * Field isControlOnScreen. + */ + private boolean isControlOnScreen; + /** + * Field humanDuration. + */ + private String humanDuration; + + private String title; + + /** + * Constructor for MediaControlInputCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public MediaControlInputCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + this.subject = "mediacontrol"; + + if (tokenizedCommand.length > 2 && tokenizedCommand[2].equals("mediacontrol")) { + this.jsonToMembers(this.parseJSONObject(tokenizedCommand[3])); + } else { + throw new CommandParseException("not mediacontrol event"); + } + } + + /** + * Constructor for MediaControlInputCommand. + * @param timeIndex long + */ + public MediaControlInputCommand(long timeIndex) { + super(); + this.subject = "mediacontrol"; + this.timeindex = timeIndex; + } + + /** + * Method jsonToMembers. + * @param payload JSONObject + * @throws CommandParseException + */ + private void jsonToMembers(JSONObject payload) throws CommandParseException { + if (payload == null) { + throw new CommandParseException("no payload found"); + } + try { + if (payload.has("state")) { + this.state = payload.getInt("state"); + } + if (payload.has("timeindex")) { + this.timeindex = payload.getDouble("timeindex"); + } + if (payload.has("duration")) { + this.duration = payload.getDouble("duration"); + } + if (payload.has("isControlOnScreen")) { + this.isControlOnScreen = payload.getBoolean("isControlOnScreen"); + } + if (payload.has("humanDuration")) { + this.setHumanDuration(payload.getString("humanDuration")); + } + if (payload.has("title")) { + this.title = payload.getString("title"); + } + } catch (JSONException e) { + e.printStackTrace(); + throw new CommandParseException("Error parsing JSON"); + } + } + + /** + * Method membersToJSON. + * @return JSONObject + */ + private JSONObject membersToJSON() { + JSONObject retval = new JSONObject(); + + try { + retval.put("state", this.state); + retval.put("title", this.state); + retval.put("timeindex", this.timeindex); + retval.put("duration", this.duration); + retval.put("isControlOnScreen", this.isControlOnScreen); + retval.put("humanDuration", this.humanDuration); + } catch (JSONException e) { + Utilities.log("ERROR CREATING JSON MESSAGE"); + } + + return retval; + } + + /** + * Method toCommandString. + * @return String + */ + @Override + public String toCommandString() { + StringBuffer sb = new StringBuffer(); + sb.append(this.method); + sb.append("|INPUT|mediacontrol|"); + sb.append(this.membersToJSON()); + sb.append("|END"); + + return sb.toString(); + } + + @Override + public String getPayload() { + return this.membersToJSON().toString(); + } + + /** + * Method getState. + * @return int + */ + public int getState() { + return state; + } + + /** + * Method setState. + * @param state int + */ + public void setState(int state) { + this.state = state; + } + + /** + * Method getTimeindex. + * @return double + */ + public double getTimeindex() { + return timeindex; + } + + /** + * Method setTimeindex. + * @param timeindex double + */ + public void setTimeindex(double timeindex) { + this.timeindex = timeindex; + } + + /** + * Method getDuration. + * @return double + */ + public double getDuration() { + return duration; + } + + /** + * Method setDuration. + * @param duration double + */ + public void setDuration(double duration) { + this.duration = duration; + } + + /** + * Method isControlOnScreen. + * @return boolean + */ + public boolean isControlOnScreen() { + return isControlOnScreen; + } + + /** + * Method setControlOnScreen. + * @param isControlOnScreen boolean + */ + public void setControlOnScreen(boolean isControlOnScreen) { + this.isControlOnScreen = isControlOnScreen; + } + + /** + * Method setHumanDuration. + * @param humanDuration String + */ + public void setHumanDuration(String humanDuration) { + this.humanDuration = humanDuration; + } + + /** + * Method getHumanDuration. + * @return String + */ + public String getHumanDuration() { + return humanDuration; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/MediaLaunchServiceCommand.java b/src/com/yahoo/connectedtv/ycommand/MediaLaunchServiceCommand.java new file mode 100644 index 0000000..da7a35b --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/MediaLaunchServiceCommand.java @@ -0,0 +1,204 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Wraps an incoming media Launch Command + *

+ * Example Command: + * + *

+ * {@literal
+ * PUBLISH|SERVICE|medialaunch|{"mimetype":"text/url","name":"Yahoo! Homepage", "url":"http://yahoo.com"}|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class MediaLaunchServiceCommand extends AbstractServiceCommand { + /** + * Field serialVersionUID. + * (value is 4422898638031538717) + */ + private static final long serialVersionUID = 4422898638031538717L; + /** + * Field CATEGORY. + * (value is ""medialaunch"") + */ + public final static String CATEGORY = "medialaunch"; + + /** + * Field mimetype. + */ + private String mimetype; + /** + * Field url. + */ + private String url; + /** + * Field title. + */ + private String title; + + /** + * Constructor for MediaLaunchServiceCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public MediaLaunchServiceCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + this.subject = "medialaunch"; + this.jsonToMembers(this.parseJSONObject(tokenizedCommand[3])); + } + + /** + * Method jsonToMembers. + * @param payload JSONObject + * @throws CommandParseException + */ + private void jsonToMembers(JSONObject payload) throws CommandParseException { + if (payload == null) { + throw new CommandParseException("no payload found"); + } + try { + if (payload.has("url")) { + this.url = payload.getString("url"); + } + if (payload.has("title")) { + this.title = payload.getString("title"); + } + if (payload.has("mimetype")) { + this.mimetype = payload.getString("mimetype"); + } + } catch (JSONException e) { + e.printStackTrace(); + throw new CommandParseException("Error parsing JSON"); + } + } + + /** + * Method membersToJSON. + * @return JSONObject + */ + private JSONObject membersToJSON() { + JSONObject retval = new JSONObject(); + + try { + retval.put("url", this.url); + retval.put("title", this.title); + retval.put("mimetype", this.mimetype); + } catch (JSONException e) { + Utilities.log("ERROR CREATING JSON MESSAGE"); + } + + return retval; + } + + /** + * Method toCommandString. + * @return String + */ + @Override + public String toCommandString() { + StringBuffer sb = new StringBuffer(); + sb.append(this.method); + sb.append("|SERVICE|medialaunch|"); + sb.append(this.membersToJSON()); + sb.append("|END"); + + return sb.toString(); + } + + @Override + public String getPayload() { + return this.membersToJSON().toString(); + } + + /** + * Method getUrl. + * @return String + */ + public String getUrl() { + return url; + } + + /** + * Method setUrl. + * @param url String + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * Method getTitle. + * @return String + */ + public String getTitle() { + return title; + } + + /** + * Method setTitle. + * @param title String + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Method getMimetype. + * @return String + */ + public String getMimetype() { + return mimetype; + } + + /** + * Method setMimetype. + * @param mimetype String + */ + public void setMimetype(String mimetype) { + this.mimetype = mimetype; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/NavigationInputCommand.java b/src/com/yahoo/connectedtv/ycommand/NavigationInputCommand.java new file mode 100644 index 0000000..6efa1b8 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/NavigationInputCommand.java @@ -0,0 +1,228 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Wraps an protocol navigation command command + *

+ * Example Commands: + * + *

+ * {@literal
+ * PUBLISH|INPUT|navigation|press_up|END
+ * PUBLISH|INPUT|navigation|press_down|END
+ * PUBLISH|INPUT|navigation|down_left|END
+ * PUBLISH|INPUT|navigation|up_left|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class NavigationInputCommand extends AbstractInputCommand { + /** + * + */ + private static final long serialVersionUID = 5761951643862994566L; + /** + * Field CATEGORY. + * (value is ""navigation"") + */ + public final static String CATEGORY = "navigation"; + + /** + * Field UP. + * (value is ""up"") + */ + public final static String UP = "up"; + /** + * Field DOWN. + * (value is ""down"") + */ + public final static String DOWN = "down"; + /** + * Field LEFT. + * (value is ""left"") + */ + public final static String LEFT = "left"; + /** + * Field RIGHT. + * (value is ""right"") + */ + public final static String RIGHT = "right"; + /** + * Field ENTER. + * (value is ""enter"") + */ + public final static String ENTER = "enter"; + /** + * Field YAHOO. + * (value is ""yahoo"") + */ + public final static String YAHOO = "yahoo"; + /** + * Field DOCK. + * (value is ""yahoo"") + */ + public final static String DOCK = "yahoo"; + /** + * Field BACK. + * (value is ""back"") + */ + public final static String BACK = "back"; + /** + * Field RED. + * (value is ""red"") + */ + public final static String RED = "red"; + /** + * Field GREEN. + * (value is ""green"") + */ + public final static String GREEN = "green"; + /** + * Field YELLOW. + * (value is ""yellow"") + */ + public final static String YELLOW = "yellow"; + /** + * Field BLUE. + * (value is ""blue"") + */ + public final static String BLUE = "blue"; + /** + * Field REWIND. + * (value is ""rewind"") + */ + public final static String REWIND = "rewind"; + /** + * Field PAUSE. + * (value is ""pause"") + */ + public final static String PAUSE = "pause"; + /** + * Field PLAY. + * (value is ""play"") + */ + public final static String PLAY = "play"; + /** + * Field STOP. + * (value is ""stop"") + */ + public final static String STOP = "stop"; + /** + * Field FASTFOWARD. + * (value is ""fastfoward"") + * @deprecated I can't spell or read the spec correctly, use FORWARD instead + */ + public final static String FASTFOWARD = "forward"; + + public final static String FORWARD = "forward"; + /** + * Field EXIT. + * (value is ""exit"") + */ + public final static String EXIT = "exit"; + + /** + * Field command. + */ + private String command; + + /** + * Constructor for NavigationInputCommand. + * @param command String + */ + public NavigationInputCommand(String command) { + super(); + this.subject = NavigationInputCommand.CATEGORY; + this.command = command; + } + + /** + * Constructor for NavigationInputCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public NavigationInputCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + + if (tokenizedCommand.length > 2 && tokenizedCommand[2].equals(NavigationInputCommand.CATEGORY)) { + this.subject = NavigationInputCommand.CATEGORY; + this.command = tokenizedCommand[3]; + } else { + throw new CommandParseException("not keyboard event"); + } + } + + /** + * Method toCommandString. + * @return String + */ + public String toCommandString() { + StringBuffer sb = new StringBuffer(); + sb.append(this.method); + sb.append("|INPUT|navigation|"); + sb.append(this.command); + sb.append("|END"); + + return sb.toString(); + } + + @Override + public String getPayload() { + return this.command; + } + + /** + * Method getCommand. + * @return String + */ + public String getCommand() { + return command; + } + + /** + * Method setCommand. + * @param command String + */ + public void setCommand(String command) { + this.command = command; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/ResetSessionCommand.java b/src/com/yahoo/connectedtv/ycommand/ResetSessionCommand.java new file mode 100644 index 0000000..7855119 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/ResetSessionCommand.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Wraps an protocol reset session command + *

+ * Example Command: + * + *

+ * {@literal
+ * SESSION|RESET|f477f88f-a4a5-4d03-9bdd-70599d683985|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class ResetSessionCommand extends AbstractSessionCommand { + /** + * Field serialVersionUID. + * (value is -4620256791049568967) + */ + private static final long serialVersionUID = -4620256791049568967L; + /** + * Field instanceId. + */ + String instanceId; + + /** + * @param instanceId + * The instanceId that was previously issued from a Session + * Create + */ + public ResetSessionCommand(String instanceId) { + super("SESSION"); + assert (instanceId != null); + this.instanceId = instanceId; + } + + /** + * Method toCommandString. + * @return String + */ + @Override + public String toCommandString() { + return String.format("SESSION|RESET|%s|END", this.instanceId); + } + + @Override + public String getPayload() { + return instanceId; + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/StatusSessionCommand.java b/src/com/yahoo/connectedtv/ycommand/StatusSessionCommand.java new file mode 100644 index 0000000..d2ba1c7 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/StatusSessionCommand.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +/** + * Wraps an protocol check status command. This checks the current authorization + * status of a an instance id. + *

+ * Example Command: + * + *

+ * {@literal
+ * SESSION|STATUS|a44520388013d49fcdb16a6b7129bffb4e54ce99|END
+ * 
+ * SESSION|STATUS|allowed|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class StatusSessionCommand extends AbstractSessionCommand { + /** + * Field serialVersionUID. + * (value is 4880444037815074358) + */ + private static final long serialVersionUID = 4880444037815074358L; + + /** + * Field status. + */ + String status; + + /** + * Constructor for StatusSessionCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public StatusSessionCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + if (tokenizedCommand.length > 2) { + this.status = tokenizedCommand[2]; + } + } + + /** + * Method toCommandString. + * @return String + */ + public String toCommandString() { + return String.format("SESSION|STATUS|%s|END", this.status); + } + + @Override + public String getPayload() { + return status; + } + + /** + * Get the unique ID assigned to the client device + * + + * @return unique id */ + public String getStatus() { + return status; + } + + /** + * Method setStatus. + * @param status String + */ + public void setStatus(String status) { + this.status = status; + } + + /** + * Method isAuthorized. + * @return boolean + */ + public boolean isAuthorized() { + return status.equals("allowed"); + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/SubscribedCommand.java b/src/com/yahoo/connectedtv/ycommand/SubscribedCommand.java new file mode 100644 index 0000000..af7ec0a --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/SubscribedCommand.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +/** + * + */ +package com.yahoo.connectedtv.ycommand; + +/** + * Handles responses to SUBSCRIBE commands. + * + * @author jecortez + * @version $Revision: 1.0 $ + */ +public class SubscribedCommand extends AbstractCommand { + /** + * Field serialVersionUID. + * (value is -3295890644028390272) + */ + private static final long serialVersionUID = -3295890644028390272L; + /** + * Field subject. + */ + private String subject; + + /** + * @param tokenizedCommand + + * @throws CommandParseException */ + public SubscribedCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + + if (tokenizedCommand.length < 2) { + throw new CommandParseException("Wrong number of tokens"); + } else if(tokenizedCommand[1] == null || tokenizedCommand[1].equals("")){ + throw new CommandParseException("type cannot be null or empty"); + } else if(tokenizedCommand[2] == null || tokenizedCommand[2].equals("")){ + throw new CommandParseException("subject cannot be null or empty"); + } + + this.type = tokenizedCommand[1]; + this.subject = tokenizedCommand[2]; + } + + /* + * (non-Javadoc) + * + * @see com.yahoo.connectedtv.ycommand.AbstractCommand#toCommandString() + */ + @Override + public String toCommandString() { + return String.format("SUBSCRIBED|%s|%s|END", this.type, this.subject); + } + + @Override + public String getPayload() { + return null; + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/UnsubscribedCommand.java b/src/com/yahoo/connectedtv/ycommand/UnsubscribedCommand.java new file mode 100644 index 0000000..90ffc2b --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/UnsubscribedCommand.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +/** + * + */ +package com.yahoo.connectedtv.ycommand; + +/** + * Handles responses to UNSUBSCRIBE commands. + * + * @author jecortez + * @version $Revision: 1.0 $ + */ +public class UnsubscribedCommand extends AbstractCommand { + /** + * Field serialVersionUID. + * (value is 5474086756242377101) + */ + private static final long serialVersionUID = 5474086756242377101L; + /** + * Field subject. + */ + private String subject; + + /** + * @param tokenizedCommand + + * @throws CommandParseException */ + public UnsubscribedCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + if (tokenizedCommand.length > 2) { + this.type = tokenizedCommand[1]; + this.subject = tokenizedCommand[2]; + } else { + throw new CommandParseException("Wrong number of tokens"); + } + } + + /* + * (non-Javadoc) + * + * @see com.yahoo.connectedtv.ycommand.AbstractCommand#toCommandString() + */ + @Override + public String toCommandString() { + return String.format("UNSUBSCRIBED|%s|%s|END", this.type, this.subject); + } + + @Override + public String getPayload() { + return null; + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/Utilities.java b/src/com/yahoo/connectedtv/ycommand/Utilities.java new file mode 100644 index 0000000..78faf30 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/Utilities.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Various helper methods + * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class Utilities { + /** + * Field DEFAULT_LOGGER_NAMESPACE. + * (value is ""com.yahoo.connectedtv.ycommand.defaultlogger"") + */ + public final static String DEFAULT_LOGGER_NAMESPACE = "com.yahoo.connectedtv.ycommand.defaultlogger"; + /** + * Field logger. + */ + private static Logger logger = null; + + /** + * Method getLogger. + * @return Logger + */ + private static Logger getLogger() { + if (Utilities.logger == null) { + Utilities.logger = Logger.getLogger(DEFAULT_LOGGER_NAMESPACE); + } + return Utilities.logger; + } + + /** + * Method setLogger. + * @param logger Logger + */ + public static void setLogger(Logger logger) { + Utilities.logger = logger; + } + + /** + * Method setLoggerEnabled. + * @param enable boolean + */ + public static void setLoggerEnabled(boolean enable) { + if (enable) { + getLogger().setLevel(Level.ALL); + } else { + getLogger().setLevel(Level.OFF); + } + } + + /** + * Method log. + * @param message Object + */ + public static void log(Object message) { + if(message == null){ + getLogger().log(Level.INFO, "null"); + }else{ + getLogger().log(Level.INFO, message.toString()); + } + + } + + /** + * Method log. + * @param level Level + * @param message Object + */ + public static void log(Level level, Object message) { + getLogger().log(level, message.toString()); + } + + public static String stringArrayToString(String[] items){ + StringBuffer sb = new StringBuffer("["); + for (int i = 0; i < items.length; i++) { + sb.append("'"); + sb.append(items[i]); + sb.append("'"); + if (i != items.length-1) + sb.append(","); + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/VideolLaunchMethodCommand.java b/src/com/yahoo/connectedtv/ycommand/VideolLaunchMethodCommand.java new file mode 100644 index 0000000..c87afa8 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/VideolLaunchMethodCommand.java @@ -0,0 +1,285 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Wraps an outgoing video Launch Command. This is a helper to generate a + * WidgetLaunchCommand. Incoming responses to these calls will be of type + * WidgetLaunchCommand. + *

+ * Example Command: + * + *

+ * {@literal
+ * CALL|widgetlaunch|null|{"payload":"{\"entries\":[{\"entries\":[{\"bitrate\":300,\"url\":\"http:\\\/\\\/209.191.64.78\\\/videos\\\/mp4-libx264-756k-480x852-libfaac-128.mp4\"}],\"title\":\"\"}],\"method\":\"launchvideo\"}","widget_id":"com.yahoo.widgets.tv.yahoovideo"}|foobar|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class VideolLaunchMethodCommand extends WidgetLaunchMethodCommand { + /** + * Field serialVersionUID. + * (value is 4422898638031538717) + */ + private static final long serialVersionUID = 4422898638031538717L; + /** + * Field WIDGET_ID. + * (value is ""com.yahoo.widgets.tv.yahoovideo"") + */ + public final static String WIDGET_ID = "com.yahoo.widgets.tv.yahoovideo"; + + /** + * Field urls. + */ + private List urls; + + /** + * @param callId + * unique id that will match responses to this call + * @param url + * video url + */ + public VideolLaunchMethodCommand(String callId, String url) { + this(callId, "", url); + } + + /** + * @param callId + * unique id that will match responses to this call + * @param title + * video titles + * @param url + * video url + */ + public VideolLaunchMethodCommand(String callId, String title, String url) { + super(callId, WIDGET_ID, null); + this.urls = new ArrayList(); + this.urls.add(new PlaylistEntry(title, url)); + } + + /** + * @param callId + * unique id that will match responses to this call + + * @param entries List + */ + public VideolLaunchMethodCommand(String callId, List entries) { + super(callId, WIDGET_ID, null); + this.urls = entries; + } + + /** + * Defines a single video entry. This mirrors how playlists work on the + * Yahoo! TV App platform. An entry has a title and a list of urls at + * different bitrates. If there is only 1 url, if is common proactice to set + * the bitrate to 300. + * @author jecortez + */ + public static class PlaylistEntry { + /** + * Field urls. + */ + public Map urls; + /** + * Field title. + */ + public String title; + + /** + * Empty playlist with empty title + */ + public PlaylistEntry() { + this.title = ""; + this.urls = new HashMap(); + } + + /** + * Defaults bitrate to 300, and sets an empty title + * + * @param url + * video url. + */ + public PlaylistEntry(String url) { + this(); + this.urls.put(300, url); + } + + /** + * Defaults bitrate to 300 + * + * @param title + * video title + * @param url + * video url + */ + public PlaylistEntry(String title, String url) { + this(); + this.title = title; + this.urls.put(300, url); + } + + /** + * Constructor for PlaylistEntry. + * @param jsonEntry JSONObject + * @throws JSONException + */ + public PlaylistEntry(JSONObject jsonEntry) throws JSONException { + if (jsonEntry.has("title")) { + this.title = jsonEntry.getString("title"); + } else { + this.title = ""; + } + if (jsonEntry.has("entries")) { + JSONArray entries = jsonEntry.getJSONArray("entries"); + for (int i = 0; i < entries.length(); i++) { + JSONObject entry = entries.getJSONObject(i); + if (entry.has("bitrate") && entry.has("url")) { + this.urls.put(Integer.valueOf(entry.getInt("bitrate")), entry.getString("url")); + } + } + } + } + + /** + * Method toJSON. + * @return JSONObject + * @throws JSONException + */ + public JSONObject toJSON() throws JSONException { + JSONObject retval = new JSONObject(); + retval.put("title", this.title); + + JSONArray entries = new JSONArray(); + for (Integer bitrate : this.urls.keySet()) { + JSONObject videoEntry = new JSONObject(); + videoEntry.put("bitrate", bitrate.intValue()); + videoEntry.put("url", this.urls.get(bitrate)); + entries.put(videoEntry); + } + retval.put("entries", entries); + + return retval; + } + } + + /** + * Method membersToJSON. + * @return JSONObject + */ + protected JSONObject membersToJSON() { + JSONObject container = new JSONObject(); + + JSONArray entries = new JSONArray(); + + try { + container.put("method", "launchvideo"); + + for (PlaylistEntry entry : this.urls) { + entries.put(entry.toJSON()); + } + + container.put("entries", entries); + + } catch (JSONException e) { + Utilities.log("ERROR CREATING JSON MESSAGE"); + } + + this.payload = container.toString(); + + return super.membersToJSON(); + } + + /** + * Method toCommandString. + * @return String + */ + @Override + public String toCommandString() { + return super.toCommandString(this.membersToJSON().toString()); + } + + /** + * Method getUrls. + * @return List + */ + public List getUrls() { + return this.urls; + } + + /** + * Method setUrls. + * @param urls List + */ + public void setUrls(List urls) { + this.urls = urls; + } + + /** + * Add a single url to the current playlist. Default bitrate to 300 and + * title to empty + * + * @param url + * video url + */ + public void addUrl(String url) { + this.urls.add(new PlaylistEntry(url)); + } + + /** + * Add a single url to the current playlist. Default bitrate to 300 + * + * @param title + * video title + * @param url + * video url + */ + public void addUrl(String title, String url) { + this.urls.add(new PlaylistEntry(title, url)); + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/WidgetCommand.java b/src/com/yahoo/connectedtv/ycommand/WidgetCommand.java new file mode 100644 index 0000000..779cb6a --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/WidgetCommand.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + + +/** + * Wraps an protocol widget communication command + *

+ * Example Commands: + * + *

+ * {@literal
+ * PUBLISH|WIDGET|com.yahoo.tv.news|{"article_title": "Foobar","article_id": "1234"}|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class WidgetCommand extends AbstractCommand { + /** + * Field serialVersionUID. (value is -3852542835125242998) + */ + private static final long serialVersionUID = -3852542835125242998L; + + /** + * Field widgetId. + */ + protected String widgetId; + /** + * Field jsonPayload. + */ + protected Object payload; + + /** + * Constructor for WidgetCommand. + * + * @param tokenizedCommand + * String[] + * @throws CommandParseException + */ + public WidgetCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + + if (tokenizedCommand.length <= 2) { + throw new CommandParseException("Wrong number of tokens"); + } else if (tokenizedCommand[1] == null || tokenizedCommand[1].equals("")) { + throw new CommandParseException("type cannot be null or empty"); + } else if (tokenizedCommand[2] == null || tokenizedCommand[2].equals("")) { + throw new CommandParseException("subject cannot be null or empty"); + } else if (tokenizedCommand[3] == null) { + throw new CommandParseException("subject cannot be null"); + } else if (tokenizedCommand[2].equals(AbstractCommand.TYPE_WIDGET)) { + throw new CommandParseException("invalid event"); + } + + this.type = AbstractCommand.TYPE_WIDGET; + this.widgetId = tokenizedCommand[2]; + this.payload = tokenizedCommand[3]; + } + + /** + * Constructor for WidgetCommand. + * + * @param widgetId + * String + * @param json_payload + * JSONObject + */ + public WidgetCommand(String widgetId, Object json_payload) { + super("PUBLISH"); + this.type = AbstractCommand.TYPE_WIDGET; + this.widgetId = widgetId; + this.payload = json_payload; + } + + /** + * Method toCommandString. + * + * @return String + */ + @Override + public String toCommandString() { + StringBuffer sb = new StringBuffer(); + sb.append(this.method); + sb.append("|WIDGET|"); + sb.append(widgetId); + sb.append('|'); + sb.append(this.payload.toString()); + sb.append("|END"); + + return sb.toString(); + } + + /** + * Method getWidgetId. + * + * @return String + */ + public String getWidgetId() { + return widgetId; + } + + /** + * Method setWidgetId. + * + * @param widgetId + * String + */ + public void setWidgetId(String widgetId) { + this.widgetId = widgetId; + } + + /** + * Method getJsonPayload. + * + * @return JSONObject + */ + public String getPayload() { + return this.payload.toString(); + } + + /** + * Method setJson_payload. + * + * @param json_payload + * JSONObject + */ + public void setPayload(Object json_payload) { + this.payload = json_payload; + } +} diff --git a/src/com/yahoo/connectedtv/ycommand/WidgetLaunchMethodCommand.java b/src/com/yahoo/connectedtv/ycommand/WidgetLaunchMethodCommand.java new file mode 100644 index 0000000..19aa8a5 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/WidgetLaunchMethodCommand.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Wraps an Widget Launch Command + *

+ * Example Command: + * + *

+ * {@literal
+ * CALL|widgetlaunch|null|{"widget_id":"com.yahoo.widgets.tv.settings"}|3534245|END
+ * 
+ * RETURN|widgetlaunch|com.yahoo.widgets.tv.settings|{status:"launched"}|3534245|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class WidgetLaunchMethodCommand extends AbstractMethodCommand { + /** + * Field serialVersionUID. + * (value is 4422898638031538717) + */ + private static final long serialVersionUID = 4422898638031538717L; + + /** + * Field CATEGORY. + * (value is ""widgetlaunch"") + */ + public final static String CATEGORY = "widgetlaunch"; + + /** + * Field launchingWidgetId. + */ + private String launchingWidgetId; + /** + * Field payload. + */ + protected String payload; + + // private boolean installWidget = false; + // private String minVersion = ""; + + /** + * Constructor for WidgetLaunchMethodCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public WidgetLaunchMethodCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + System.out.println("not doing anything with incoming widget launch"); + } + + /** + * @param callId + * unique id that will match responses to this call + * @param launchingWidgetId + * widgetID of the widget you want to launch + * @param payload + * payload to be sent to the widget upon launch + */ + public WidgetLaunchMethodCommand(String callId, String launchingWidgetId, String payload) { + super("CALL", null, CATEGORY, callId); + this.launchingWidgetId = launchingWidgetId; + this.payload = payload; + } + + /** + * @param callId + * unique id that will match responses to this call + * @param launchingWidgetId + * widgetID of the widget you want to launch + */ + public WidgetLaunchMethodCommand(String callId, String launchingWidgetId) { + this(callId, launchingWidgetId, null); + } + + /** + * Method membersToJSON. + * @return JSONObject + */ + protected JSONObject membersToJSON() { + JSONObject retval = new JSONObject(); + + try { + retval.put("widget_id", this.launchingWidgetId); + if (payload != null) { + retval.put("payload", this.payload.toString()); + } + } catch (JSONException e) { + Utilities.log("ERROR CREATING JSON MESSAGE"); + } + + return retval; + } + + /** + * Method toCommandString. + * @return String + */ + @Override + public String toCommandString() { + return super.toCommandString(this.membersToJSON().toString()); + } + + @Override + public String getPayload() { + return this.membersToJSON().toString(); + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/WidgetListServiceCommand.java b/src/com/yahoo/connectedtv/ycommand/WidgetListServiceCommand.java new file mode 100644 index 0000000..99451f2 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/WidgetListServiceCommand.java @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Wraps an widget list command. + *

+ * Example Command: + * + *

+ * {@literal
+ * PUBLISH|SERVICE|widgetlist|[{id: "com.yahoo.widgets.tv.flickr",name: "Flickr", iconURL: "http://l.yimg.com/g/images/logo_home.png.v2"}]|END
+ * }
+ * 
+ * + *

+ * + * @author jecortez + * + * @version $Revision: 1.0 $ + */ +public class WidgetListServiceCommand extends AbstractServiceCommand { + /** + * Field serialVersionUID. + * (value is -1121055518342662807) + */ + private static final long serialVersionUID = -1121055518342662807L; + + /** + * Field CATEGORY. + * (value is ""widgetlist"") + */ + public final static String CATEGORY = "widgetlist"; + + /** + * Field widgets. + */ + private List> widgets; + + /** + * Constructor for WidgetListServiceCommand. + * @param tokenizedCommand String[] + * @throws CommandParseException + */ + public WidgetListServiceCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + + if (tokenizedCommand.length > 2 && tokenizedCommand[2].equals(CATEGORY)) { + this.subject = WidgetListServiceCommand.CATEGORY; + widgets = new LinkedList>(); + try { + this.jsonToMembers(new JSONArray((tokenizedCommand[3]))); + } catch (JSONException e) { + e.printStackTrace(); + throw new CommandParseException("unable to parse JSON"); + } + } else { + throw new CommandParseException("not widgetlist event"); + } + } + + /** + * Method jsonToMembers. + * @param payload JSONArray + * @throws CommandParseException + */ + private void jsonToMembers(JSONArray payload) throws CommandParseException { + try { + for (int i = 0; i < payload.length(); i++) { + JSONObject entry = payload.getJSONObject(i); + Map widget = new HashMap(); + if (entry.has("id")) { + widget.put("widgetID", entry.getString("id")); + } + + if (entry.has("name")) { + widget.put("name", entry.getString("name")); + } + + if (entry.has("iconURL")) { + widget.put("iconURL", entry.getString("iconURL")); + } + widgets.add(widget); + } + } catch (JSONException e) { + e.printStackTrace(); + throw new CommandParseException("Error parsing JSON"); + } + } + + /** + * Method membersToJSON. + * @return JSONArray + */ + private JSONArray membersToJSON() { + JSONArray retaval = new JSONArray(); + for(Map item: this.widgets){ + try { + JSONObject itemObj = new JSONObject(); + itemObj.put("id", item.get("widgetID")); + itemObj.put("name", item.get("name")); + itemObj.put("iconURL", item.get("iconURL")); + retaval.put(itemObj); + } catch (JSONException e) { + e.printStackTrace(); + } + } + return retaval; + } + + /** + * Method toCommandString. + * @return String + */ + public String toCommandString() { + return String.format("PUBLISH|SERVICE|widgetlist|%s|END", this.membersToJSON().toString()); + } + + @Override + public String getPayload() { + return this.membersToJSON().toString(); + } + + /** + * Method getWidgets. + * @return List> + */ + public List> getWidgets() { + return widgets; + } + + /** + * Method setWidgets. + * @param widgets List> + */ + public void setWidgets(List> widgets) { + this.widgets = widgets; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/WidgetMethodCommand.java b/src/com/yahoo/connectedtv/ycommand/WidgetMethodCommand.java new file mode 100644 index 0000000..e16bf8f --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/WidgetMethodCommand.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + + +/** + * Wraps an Widget Launch Command + *

+ * Example Command: + *

+ *

+ * {@literal
+ * CALL|fetchVideos|com.yahoo.widgets.tv.video|{"foo":"bar"}|3534245|END
+ *
+ * RETURN|fetchVideos|com.yahoo.widgets.tv.settings|{status:"launched"}|3534245|END
+ * }
+ * 
+ *

+ *

+ * + * @author jecortez + * @version $Revision: 1.0 $ + */ +public class WidgetMethodCommand extends AbstractMethodCommand { + /** + * Field serialVersionUID. (value is 4422898638031538717) + */ + private static final long serialVersionUID = 4422898638031538717L; + + /** + * Field payload. + */ + protected String payload; + + public WidgetMethodCommand(String[] tokenizedCommand) throws CommandParseException { + super(tokenizedCommand); + this.payload = tokenizedCommand[4]; + } + + /** + * + * + * @param callId unique id that will match responses to this call + * @param payload payload to be sent to the widget upon launch + * @param type + * @param remoteMethod + * @param widgetId + */ + public WidgetMethodCommand(String type, String remoteMethod, String callId, String widgetId, String payload) { + super(type, widgetId, remoteMethod, callId); + this.payload = payload != null ? payload : ""; + } + + /** + * Method toCommandString. + * + * @return String + */ + @Override + public String toCommandString() { + return super.toCommandString(this.payload.toString()); + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload = payload; + } + +} diff --git a/src/com/yahoo/connectedtv/ycommand/package.html b/src/com/yahoo/connectedtv/ycommand/package.html new file mode 100644 index 0000000..9dfb1a7 --- /dev/null +++ b/src/com/yahoo/connectedtv/ycommand/package.html @@ -0,0 +1,14 @@ + + +

+Helper library for the Yahoo! Connected TV Device Communication Protocol. +

+

+Yahoo! Connected TV’s device communication model enables new and exciting TV-viewing experiences driven by the sophisticated controls available on today’s tablets, laptops, and mobile phones. A new generation of TV Widget capabilities is now available to consumers, including gesture-based, multi-display, and multi-user features for intensive gaming, multimedia, and social applications.
+This device communication platform supports two-way message-passing between Internet-enabled devices and Connected TVs running the Yahoo! Widget Engine. Keyboard, navigation, and widget-specific messages can be communicated through a new protocol over a local network. +

+ +

+ + + diff --git a/tests/com/yahoo/connectedtv/ycommand/AuthSessionCommandTest.java b/tests/com/yahoo/connectedtv/ycommand/AuthSessionCommandTest.java new file mode 100644 index 0000000..803d3d9 --- /dev/null +++ b/tests/com/yahoo/connectedtv/ycommand/AuthSessionCommandTest.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class AuthSessionCommandTest { + public static final String certificate = "-----BEGIN CERTIFICATE-----\n" + + "MIIDBzCCAhigAwIBAwIBADANBgkqhkiG9w0BAQQFADByMQswCQYDVQQGEwJVUzET\n" + + "MBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJU3Vubnl2YWxlMQ4wDAYDVQQK\n" + + "EwVZYWhvbzEVMBMGA1UECxMMQ29ubmVjdGVkIFRWMRMwEQYDVQQDEwpUViBXaWRn\n" + + "ZXRzMB4XDTExMDQxODIzMDkzMloXDTEyMDQxNzIzMDkzMlowcjELMAkGA1UEBhMC\n" + + "VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1bm55dmFsZTEOMAwG\n" + + "A1UEChMFWWFob28xFTATBgNVBAsTDENvbm5lY3RlZCBUVjETMBEGA1UEAxMKVFYg\n" + + "V2lkZ2V0czCB9jANBgkqhkiG9w0BAQEFAAOB5AAwgeACgdgDOIEIEJuyW5rRwI2H\n" + + "Bj4epGVYeaz0sWB0T5o745XmkRpJbiNpFtoe0ookjiUDrtnLTHxUz1Xpy24Cccbr\n" + + "KlwC95N8ZdR0W+DsBhC1r4zluwCHgAy1h/rwXAUfMdlqNud5XIAaZTZgdO8E3T3N\n" + + "/ImRopzy801PvVUIHZY7gvVTAQF2nC6ycmAzKPViLW2paFOB+dOSYPLBURmoMJyK\n" + + "E5YzcI+oO8JK/n59siAZj8jCNVa/bpd8Ktmv68T0xWRF8x6yWnxV2fMr2Qucwa6c\n" + + "hNQntbeehFpqmOcCAwEAATANBgkqhkiG9w0BAQQFAAOB2QABGdBWpdSVS3gfYXHS\n" + + "VfwZ8ErrU6kv/y5OL/1Ug+pnVOcJEwTO8iSAaxJckpD6+v3i5VroRtgVsv0RjZ6I\n" + + "7egWdFDGF2Q8Qz8xu4IprKhc5ItBle9hwHai1j5d4y9iOit4+MHJGrIWzhDh4o8h\n" + + "AW8c6RPLZ3Jdx7C54qqVcj1dUhgC/gD4SYShnAf2RY64QbNXAGsw9FzaEhpPGfMQ\n" + + "XtbrPdDE5QkYueUqwUx7L1NWZJ8d4XbZQZdRvo4ZA86k3qlLQd+lkpiWtSzHZrPV\n" + "IUk138PVETXYt18=\n" + + "-----END CERTIFICATE-----"; + + @Test + public void testAuthSessionCommand() { + AuthSessionCommand asc = new AuthSessionCommand("fake", certificate); + assertEquals(asc.getType(), "SESSION"); + } + + @Test + public void testToCommandString() { + AuthSessionCommand asc = new AuthSessionCommand("fake", certificate); + assertEquals("SESSION|AUTH|a37336e0dea3bf2e2e8ecafa81dad0e8d69d18e2|END", asc.toCommandString()); + } + + @Test + public void testGetUserCode() { + AuthSessionCommand asc = new AuthSessionCommand("fake", certificate); + assertEquals("fake", asc.getUserCode()); + } + + @Test + public void testSetUserCode() { + AuthSessionCommand asc = new AuthSessionCommand("fake", certificate); + asc.setUserCode("noms"); + assertEquals("noms", asc.getUserCode()); + } + + @Test + public void testGetCert() { + AuthSessionCommand asc = new AuthSessionCommand("fake", certificate); + assertEquals(certificate, asc.getCert()); + } + + @Test + public void testSetCert() { + AuthSessionCommand asc = new AuthSessionCommand("fake", certificate); + asc.setCert("foobar"); + assertEquals("foobar", asc.getCert()); + } + +} diff --git a/tests/com/yahoo/connectedtv/ycommand/CommandParserTest.java b/tests/com/yahoo/connectedtv/ycommand/CommandParserTest.java new file mode 100644 index 0000000..63d2653 --- /dev/null +++ b/tests/com/yahoo/connectedtv/ycommand/CommandParserTest.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.hamcrest.CoreMatchers; +import org.junit.Test; + +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class CommandParserTest { + + @Test + public void testSessionGrantedParse() throws CommandParseException { + String command = "SESSION|GRANTED|1234567|END"; + assertThat(CommandParser.parse(command), CoreMatchers.instanceOf(GrantedSessionCommand.class)); + } + + @Test + public void testErrorParse() throws CommandParseException { + String command = "ERROR|003|Invalid Signature|END"; + assertThat(CommandParser.parse(command), CoreMatchers.instanceOf(ErrorCommand.class)); + } + + @Test + public void testKeyboardParse() throws CommandParseException { + String command = "PUBLISH|INPUT|keyboard|{\"totalValue\": \"hi\", \"key\": \"\", \"keyCode\":13, \"cursorPosition\": 2, \"modifiers\": {\"control\": false, \"shift\": false, \"alt\": false}, \"layoutType\": 1}|END"; + assertThat(CommandParser.parse(command), CoreMatchers.instanceOf(KeyboardInputCommand.class)); + } + + @Test + public void testMediaControlParse() throws CommandParseException { + String command = "PUBLISH|INPUT|mediacontrol|{\"state\": \"5\", timeindex: \"123456\", duration:1234567, isControlOnScreen: true, humanDuration: \"00:01:42\"}|END"; + assertThat(CommandParser.parse(command), CoreMatchers.instanceOf(MediaControlInputCommand.class)); + } + + @Test + public void testWidgetParse() throws CommandParseException { + String command = "PUBLISH|WIDGET|com.yahoo.connectedtv.tests|{}|END"; + assertThat(CommandParser.parse(command), CoreMatchers.instanceOf(WidgetCommand.class)); + } + + @Test + public void testSubscribedParse() throws CommandParseException { + String command = "SUBSCRIBED|INPUT|keyboard|END"; + assertThat(CommandParser.parse(command), CoreMatchers.instanceOf(SubscribedCommand.class)); + } + + @Test + public void testUnsubscribedParse() throws CommandParseException { + String command = "UNSUBSCRIBED|INPUT|keyboard|END"; + assertThat(CommandParser.parse(command), CoreMatchers.instanceOf(UnsubscribedCommand.class)); + } + + @Test(expected = CommandParseException.class) + public void testJunkDataParse() throws CommandParseException { + String command = "0Ha1Th3r!|END> entries = new ArrayList(); + entries.add(entry); + VideolLaunchMethodCommand vlmc = new VideolLaunchMethodCommand("foobar", entries); + assertEquals("CALL", vlmc.getMethod()); + assertEquals("widgetlaunch", vlmc.getSubject()); + assertEquals(null, vlmc.getWidgetID()); + assertEquals("foobar", vlmc.getCallId()); + + VideolLaunchMethodCommand.PlaylistEntry newentry = vlmc.getUrls().get(0); + assertEquals("http://yahoo.com", newentry.urls.get(newentry.urls.keySet().toArray()[0])); + assertEquals("Yahoo!", newentry.title); + } + +} diff --git a/tests/com/yahoo/connectedtv/ycommand/WidgetCommandTest.java b/tests/com/yahoo/connectedtv/ycommand/WidgetCommandTest.java new file mode 100644 index 0000000..c211ab3 --- /dev/null +++ b/tests/com/yahoo/connectedtv/ycommand/WidgetCommandTest.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class WidgetCommandTest { + + @Test + public void testToCommandString() throws CommandParseException, JSONException { + String[] tokenizedCommand = { "PUBLISH", "WIDGET", "com.yahoo.tv.news", + "{\"article_title\": \"Foobar\",\"article_id\": \"1234\"}", "END" }; + + WidgetCommand wsc = new WidgetCommand(tokenizedCommand); +// assertEquals("PUBLISH|WIDGET|com.yahoo.tv.news|{\"article_id\":\"1234\",\"article_title\":\"Foobar\"}|END", + //wsc.toCommandString()); + wsc.setWidgetId("com.yahoo.tv.sports"); + assertEquals("com.yahoo.tv.sports", wsc.getWidgetId()); + wsc.setPayload(new JSONObject("{\"article_id\":\"1111\",\"article_title\":\"Foobar\"}")); + assertEquals("{\"article_title\":\"Foobar\",\"article_id\":\"1111\"}", wsc.getPayload().toString()); + } + + @Test + public void testWidgetCommand() throws JSONException { + JSONObject jsonPayload = new JSONObject("{\"article_title\": \"Foobar\",\"article_id\": \"1234\"}"); + WidgetCommand wsc = new WidgetCommand("com.yahoo.tv.news", jsonPayload); + assertEquals("PUBLISH|WIDGET|com.yahoo.tv.news|{\"article_id\":\"1234\",\"article_title\":\"Foobar\"}|END", + wsc.toCommandString()); + } + +} diff --git a/tests/com/yahoo/connectedtv/ycommand/WidgetLaunchMethodCommandTest.java b/tests/com/yahoo/connectedtv/ycommand/WidgetLaunchMethodCommandTest.java new file mode 100644 index 0000000..079ac5a --- /dev/null +++ b/tests/com/yahoo/connectedtv/ycommand/WidgetLaunchMethodCommandTest.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class WidgetLaunchMethodCommandTest { + + @Test + public void testToCommandString() throws JSONException { + WidgetLaunchMethodCommand vlmc = new WidgetLaunchMethodCommand( + "foobar", + "com.yahoo.widgets.tv.yahoovideo", + "{\"entries\":[{\"title\":\"Yahoo!\",\"entries\":[{\"bitrate\":300,\"url\":\"http://yahoo.com\"}]}],\"method\":\"launchvideo\"}"); + + String[] cmd = vlmc.toCommandString().split("\\|"); + + assertEquals("CALL", cmd[0]); + assertEquals("widgetlaunch", cmd[1]); + assertEquals("null", cmd[2]); + JSONObject response = new JSONObject(cmd[3]); + assertEquals("com.yahoo.widgets.tv.yahoovideo", response.getString("widget_id")); + assertEquals( + "{\"entries\":[{\"title\":\"Yahoo!\",\"entries\":[{\"bitrate\":300,\"url\":\"http://yahoo.com\"}]}],\"method\":\"launchvideo\"}", + response.getString("payload")); + assertEquals("foobar", cmd[4]); + } + + // @Test + // public void testVideolLaunchMethodCommandStringArray() { + // fail("Not yet implemented"); + // } + + // @Test + // public void testVideolLaunchMethodCommandStringString() { + // WidgetLaunchMethodCommand vlmc = new WidgetLaunchMethodCommand("foobar", + // "http://yahoo.com"); + // assertEquals("CALL", vlmc.getMethod()); + // assertEquals("widgetlaunch", vlmc.getSubject()); + // assertEquals(null, vlmc.getWidgetID()); + // assertEquals("foobar", vlmc.getCallId()); + // VideolLaunchMethodCommand.PlaylistEntry entry = vlmc.getUrls().get(0); + // assertEquals("http://yahoo.com", + // entry.urls.get(entry.urls.keySet().toArray()[0])); + // } + // + // @Test + // public void testVideolLaunchMethodCommandStringStringString() { + // VideolLaunchMethodCommand vlmc = new VideolLaunchMethodCommand("foobar", + // "Yahoo!", "http://yahoo.com"); + // assertEquals("CALL", vlmc.getMethod()); + // assertEquals("widgetlaunch", vlmc.getSubject()); + // assertEquals(null, vlmc.getWidgetID()); + // assertEquals("foobar", vlmc.getCallId()); + // VideolLaunchMethodCommand.PlaylistEntry entry = vlmc.getUrls().get(0); + // assertEquals("http://yahoo.com", + // entry.urls.get(entry.urls.keySet().toArray()[0])); + // assertEquals("Yahoo!", entry.title); + // } + // + // @Test + // public void + // testVideolLaunchMethodCommandStringStringListOfPlaylistEntry() { + // VideolLaunchMethodCommand.PlaylistEntry entry = new + // VideolLaunchMethodCommand.PlaylistEntry("Yahoo!", + // "http://yahoo.com"); + // List entries = new + // ArrayList(); + // entries.add(entry); + // VideolLaunchMethodCommand vlmc = new VideolLaunchMethodCommand("foobar", + // entries); + // assertEquals("CALL", vlmc.getMethod()); + // assertEquals("widgetlaunch", vlmc.getSubject()); + // assertEquals(null, vlmc.getWidgetID()); + // assertEquals("foobar", vlmc.getCallId()); + // + // VideolLaunchMethodCommand.PlaylistEntry newentry = vlmc.getUrls().get(0); + // assertEquals("http://yahoo.com", + // newentry.urls.get(newentry.urls.keySet().toArray()[0])); + // assertEquals("Yahoo!", newentry.title); + // } + +} diff --git a/tests/com/yahoo/connectedtv/ycommand/WidgetListCommandTest.java b/tests/com/yahoo/connectedtv/ycommand/WidgetListCommandTest.java new file mode 100644 index 0000000..6b53a89 --- /dev/null +++ b/tests/com/yahoo/connectedtv/ycommand/WidgetListCommandTest.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2011, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, + * with or without modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior + * written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ +package com.yahoo.connectedtv.ycommand; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class WidgetListCommandTest { + +// @Test +// public void testToCommandString() throws CommandParseException { +// String[] tokenizedCommand = { "PUBLISH", "SERVICE", "widgetlist", +// "[{id: \"com.yahoo.widgets.tv.flickr\",name: \"Flickr\", iconURL: \"http://l.yimg.com/g/images/logo_home.png.v2\"}]" }; +// WidgetListServiceCommand wlsc = new WidgetListServiceCommand(tokenizedCommand); +// assertEquals( +// "PUBLISH|SERVICE|widgetlist|[{\"name\":\"Flickr\",\"widgetID\":\"com.yahoo.widgets.tv.flickr\",\"iconURL\":\"http://l.yimg.com/g/images/logo_home.png.v2\"}]|END", +// wlsc.toCommandString()); +// } + + @Test + public void testNavigationInputCommandStringArray() throws CommandParseException { + String[] tokenizedCommand = { "PUBLISH", "SERVICE", "widgetlist", + "[{id: \"com.yahoo.widgets.tv.flickr\",name: \"Flickr\", iconURL: \"http://l.yimg.com/g/images/logo_home.png.v2\"}]" }; + WidgetListServiceCommand wlsc = new WidgetListServiceCommand(tokenizedCommand); + assertEquals(wlsc.getMethod(), "PUBLISH"); + assertEquals(wlsc.getType(), "SERVICE"); + assertEquals(wlsc.getSubject(), "widgetlist"); + } + +}