From b51b45d5f408249df8f1003a52790aec23d95e6a Mon Sep 17 00:00:00 2001 From: CavariuX Date: Sun, 19 Apr 2015 11:05:24 -0500 Subject: [PATCH] ++Added | --Removed | *Extra ------------------------------------------ ++Json ++GetViewers ++isOp --Bugs --- src/tk/cavariux/twitchirc/Chat/Channel.java | 47 + src/tk/cavariux/twitchirc/Chat/User.java | 44 +- src/tk/cavariux/twitchirc/Core/TwitchBot.java | 78 +- src/tk/cavariux/twitchirc/Json/JsonArray.java | 487 ++++++++++ .../cavariux/twitchirc/Json/JsonLiteral.java | 91 ++ .../cavariux/twitchirc/Json/JsonNumber.java | 94 ++ .../cavariux/twitchirc/Json/JsonObject.java | 873 ++++++++++++++++++ .../cavariux/twitchirc/Json/JsonParser.java | 403 ++++++++ .../cavariux/twitchirc/Json/JsonString.java | 74 ++ src/tk/cavariux/twitchirc/Json/JsonValue.java | 460 +++++++++ .../cavariux/twitchirc/Json/JsonWriter.java | 153 +++ .../twitchirc/Json/ParseException.java | 71 ++ 12 files changed, 2836 insertions(+), 39 deletions(-) create mode 100644 src/tk/cavariux/twitchirc/Json/JsonArray.java create mode 100644 src/tk/cavariux/twitchirc/Json/JsonLiteral.java create mode 100644 src/tk/cavariux/twitchirc/Json/JsonNumber.java create mode 100644 src/tk/cavariux/twitchirc/Json/JsonObject.java create mode 100644 src/tk/cavariux/twitchirc/Json/JsonParser.java create mode 100644 src/tk/cavariux/twitchirc/Json/JsonString.java create mode 100644 src/tk/cavariux/twitchirc/Json/JsonValue.java create mode 100644 src/tk/cavariux/twitchirc/Json/JsonWriter.java create mode 100644 src/tk/cavariux/twitchirc/Json/ParseException.java diff --git a/src/tk/cavariux/twitchirc/Chat/Channel.java b/src/tk/cavariux/twitchirc/Chat/Channel.java index 51b3fc5..4867106 100644 --- a/src/tk/cavariux/twitchirc/Chat/Channel.java +++ b/src/tk/cavariux/twitchirc/Chat/Channel.java @@ -1,9 +1,19 @@ package tk.cavariux.twitchirc.Chat; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; + import tk.cavariux.twitchirc.Core.TwitchBot; +import tk.cavariux.twitchirc.Json.JsonArray; +import tk.cavariux.twitchirc.Json.JsonObject; +import tk.cavariux.twitchirc.Json.JsonValue; public class Channel { + private String urln = "http://tmi.twitch.tv/group/user/$channel$/chatters"; private String channel; private TwitchBot bot; @@ -82,4 +92,41 @@ public final void unhost() { this.bot.sendMessage("/unhost", this); } + + public final String[] getViewers() + { + URL url; + try { + url = new URL(urln.replace("$channel$", channel.toString().substring(1))); + System.out.println(url); + URLConnection conn = url.openConnection(); + BufferedReader br = new BufferedReader( new InputStreamReader( conn.getInputStream() )); + String inputLine = ""; + String str = ""; + while ((str = br.readLine()) != null) + { + inputLine = inputLine + str; + } + br.close(); + JsonObject jsonObj = JsonObject.readFrom(inputLine); + JsonArray array = jsonObj.get("chatters").asObject().get("viewers").asArray(); + JsonArray array2 = jsonObj.get("chatters").asObject().get("moderators").asArray(); + String[] viewers = new String[array.size() + array2.size()]; + int i = 0; + for (JsonValue value : array) + { + viewers[i] = value.toString().substring(1, value.toString().length() - 1); + i++; + } + for (JsonValue value : array2) + { + viewers[i] = value.toString().substring(1, value.toString().length() - 1); + i++; + } + return viewers; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } } diff --git a/src/tk/cavariux/twitchirc/Chat/User.java b/src/tk/cavariux/twitchirc/Chat/User.java index 9f28c48..6ca59db 100644 --- a/src/tk/cavariux/twitchirc/Chat/User.java +++ b/src/tk/cavariux/twitchirc/Chat/User.java @@ -1,8 +1,19 @@ package tk.cavariux.twitchirc.Chat; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; + +import tk.cavariux.twitchirc.Json.JsonArray; +import tk.cavariux.twitchirc.Json.JsonObject; +import tk.cavariux.twitchirc.Json.JsonValue; + public class User { - + private String urln = "http://tmi.twitch.tv/group/user/$channel$/chatters"; private String user; public User(String user) @@ -14,4 +25,35 @@ public String toString() { return user; } + + public final boolean isOp(Channel channel) + { + URL url; + try { + url = new URL(urln.replace("$channel$", channel.toString().substring(1))); + System.out.println(url); + URLConnection conn = url.openConnection(); + BufferedReader br = new BufferedReader( new InputStreamReader( conn.getInputStream() )); + String inputLine = ""; + String str = ""; + while ((str = br.readLine()) != null) + { + inputLine = inputLine + str; + } + br.close(); + JsonObject jsonObj = JsonObject.readFrom(inputLine); + JsonArray array = jsonObj.get("chatters").asObject().get("moderators").asArray(); + ArrayList mods = new ArrayList(); + for (JsonValue value : array) + { + mods.add(value.toString().substring(1, value.toString().length() - 1)); + } + if (mods.contains(this.toString())) + return true; + } catch (IOException e) { + e.printStackTrace(); + } + + return false; + } } diff --git a/src/tk/cavariux/twitchirc/Core/TwitchBot.java b/src/tk/cavariux/twitchirc/Core/TwitchBot.java index abe369b..d08ee8e 100644 --- a/src/tk/cavariux/twitchirc/Core/TwitchBot.java +++ b/src/tk/cavariux/twitchirc/Core/TwitchBot.java @@ -7,7 +7,6 @@ import java.io.OutputStreamWriter; import java.net.Socket; import java.util.ArrayList; -import java.util.Arrays; import tk.cavariux.twitchirc.Chat.Channel; import tk.cavariux.twitchirc.Chat.User; @@ -19,45 +18,50 @@ public class TwitchBot { private BufferedWriter writer; private BufferedReader reader; private ArrayList channels = new ArrayList(); - private double version = 0.01; + private String version = "v1.0-alpha"; public TwitchBot(){} - public void connect() throws IOException + public void connect() { - if (user == null || user == "") - { - System.err.println("Please select a valid Username"); - System.exit(1); - return; - } - if (oauth_key == null || oauth_key == "") - { - System.err.println("Please select a valid Oauth_Key"); - System.exit(2); - return; - } - - - @SuppressWarnings("resource") - Socket socket = new Socket("irc.twitch.tv", 6667); - this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); - this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); - - this.writer.write("PASS " + oauth_key + "\r\n"); - this.writer.write("NICK " + user + "\r\n"); - this.writer.write("USER " + "TwitchIRC v0.01 \r\n"); - this.writer.flush(); - - String line = ""; - while ((line = this.reader.readLine()) != null) + try{ + if (user == null || user == "") + { + System.err.println("Please select a valid Username"); + System.exit(1); + return; + } + if (oauth_key == null || oauth_key == "") + { + System.err.println("Please select a valid Oauth_Key"); + System.exit(2); + return; + } + + + @SuppressWarnings("resource") + Socket socket = new Socket("irc.twitch.tv", 6667); + this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + this.writer.write("PASS " + oauth_key + "\r\n"); + this.writer.write("NICK " + user + "\r\n"); + this.writer.write("USER " + this.getVersion() + " \r\n"); + this.writer.flush(); + + String line = ""; + while ((line = this.reader.readLine()) != null) + { + if (line.indexOf("004") >= 0) { + System.out.println("Connected >> " + user + " ~ irc.twitch.tv"); + break; + }else { + System.out.println(line); + } + } + } catch (IOException e) { - if (line.indexOf("004") >= 0) { - System.out.println("Connected >> " + user + " ~ irc.twitch.tv"); - break; - }else { - System.out.println(line); - } + e.printStackTrace(); } } @@ -151,8 +155,6 @@ public final void start() this.writer.flush(); } else if (line.contains("PRIVMSG")) { - /* Send The Messages to The onMessage Method */ - //System.out.println(line); String str[]; str = line.split("!"); final User msg_user = new User(str[0].substring(1, str[0].length())); @@ -178,6 +180,6 @@ public final void start() public final String getVersion() { - return "TwitchIRC v"+version; + return "TwitchIRC "+version; } } diff --git a/src/tk/cavariux/twitchirc/Json/JsonArray.java b/src/tk/cavariux/twitchirc/Json/JsonArray.java new file mode 100644 index 0000000..079294f --- /dev/null +++ b/src/tk/cavariux/twitchirc/Json/JsonArray.java @@ -0,0 +1,487 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package tk.cavariux.twitchirc.Json; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + + +/** + * Represents a JSON array, i.e. an ordered collection of JSON values. + *

+ * Elements can be added using the add(...) methods which accept instances of + * {@link JsonValue}, strings, primitive numbers, and boolean values. To replace an element of an + * array, use the set(int, ...) methods. + *

+ *

+ * Elements can be accessed by their index using {@link #get(int)}. This class also supports + * iterating over the elements in document order using an {@link #iterator()} or an enhanced for + * loop: + *

+ * + *
+ * for( JsonValue value : jsonArray ) {
+ *   ...
+ * }
+ * 
+ *

+ * An equivalent {@link List} can be obtained from the method {@link #values()}. + *

+ *

+ * Note that this class is not thread-safe. If multiple threads access a + * JsonArray instance concurrently, while at least one of these threads modifies the + * contents of this array, access to the instance must be synchronized externally. Failure to do so + * may lead to an inconsistent state. + *

+ *

+ * This class is not supposed to be extended by clients. + *

+ */ +@SuppressWarnings( "serial" ) +// use default serial UID +public class JsonArray extends JsonValue implements Iterable { + + private final List values; + + /** + * Creates a new empty JsonArray. + */ + public JsonArray() { + values = new ArrayList(); + } + + /** + * Creates a new JsonArray with the contents of the specified JSON array. + * + * @param array + * the JsonArray to get the initial contents from, must not be null + */ + public JsonArray( JsonArray array ) { + this( array, false ); + } + + private JsonArray( JsonArray array, boolean unmodifiable ) { + if( array == null ) { + throw new NullPointerException( "array is null" ); + } + if( unmodifiable ) { + values = Collections.unmodifiableList( array.values ); + } else { + values = new ArrayList( array.values ); + } + } + + /** + * Reads a JSON array from the given reader. + *

+ * Characters are read in chunks and buffered internally, therefore wrapping an existing reader in + * an additional BufferedReader does not improve reading + * performance. + *

+ * + * @param reader + * the reader to read the JSON array from + * @return the JSON array that has been read + * @throws IOException + * if an I/O error occurs in the reader + * @throws ParseException + * if the input is not valid JSON + * @throws UnsupportedOperationException + * if the input does not contain a JSON array + */ + public static JsonArray readFrom( Reader reader ) throws IOException { + return JsonValue.readFrom( reader ).asArray(); + } + + /** + * Reads a JSON array from the given string. + * + * @param string + * the string that contains the JSON array + * @return the JSON array that has been read + * @throws ParseException + * if the input is not valid JSON + * @throws UnsupportedOperationException + * if the input does not contain a JSON array + */ + public static JsonArray readFrom( String string ) { + return JsonValue.readFrom( string ).asArray(); + } + + /** + * Returns an unmodifiable wrapper for the specified JsonArray. This method allows to provide + * read-only access to a JsonArray. + *

+ * The returned JsonArray is backed by the given array and reflects subsequent changes. Attempts + * to modify the returned JsonArray result in an UnsupportedOperationException. + *

+ * + * @param array + * the JsonArray for which an unmodifiable JsonArray is to be returned + * @return an unmodifiable view of the specified JsonArray + */ + public static JsonArray unmodifiableArray( JsonArray array ) { + return new JsonArray( array, true ); + } + + /** + * Appends the JSON representation of the specified int value to the end of this + * array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JsonArray add( int value ) { + values.add( valueOf( value ) ); + return this; + } + + /** + * Appends the JSON representation of the specified long value to the end of this + * array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JsonArray add( long value ) { + values.add( valueOf( value ) ); + return this; + } + + /** + * Appends the JSON representation of the specified float value to the end of this + * array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JsonArray add( float value ) { + values.add( valueOf( value ) ); + return this; + } + + /** + * Appends the JSON representation of the specified double value to the end of this + * array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JsonArray add( double value ) { + values.add( valueOf( value ) ); + return this; + } + + /** + * Appends the JSON representation of the specified boolean value to the end of this + * array. + * + * @param value + * the value to add to the array + * @return the array itself, to enable method chaining + */ + public JsonArray add( boolean value ) { + values.add( valueOf( value ) ); + return this; + } + + /** + * Appends the JSON representation of the specified string to the end of this array. + * + * @param value + * the string to add to the array + * @return the array itself, to enable method chaining + */ + public JsonArray add( String value ) { + values.add( valueOf( value ) ); + return this; + } + + /** + * Appends the specified JSON value to the end of this array. + * + * @param value + * the JsonValue to add to the array, must not be null + * @return the array itself, to enable method chaining + */ + public JsonArray add( JsonValue value ) { + if( value == null ) { + throw new NullPointerException( "value is null" ); + } + values.add( value ); + return this; + } + + /** + * Replaces the element at the specified position in this array with the JSON representation of + * the specified int value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 or + * index >= size + */ + public JsonArray set( int index, int value ) { + values.set( index, valueOf( value ) ); + return this; + } + + /** + * Replaces the element at the specified position in this array with the JSON representation of + * the specified long value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 or + * index >= size + */ + public JsonArray set( int index, long value ) { + values.set( index, valueOf( value ) ); + return this; + } + + /** + * Replaces the element at the specified position in this array with the JSON representation of + * the specified float value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 or + * index >= size + */ + public JsonArray set( int index, float value ) { + values.set( index, valueOf( value ) ); + return this; + } + + /** + * Replaces the element at the specified position in this array with the JSON representation of + * the specified double value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 or + * index >= size + */ + public JsonArray set( int index, double value ) { + values.set( index, valueOf( value ) ); + return this; + } + + /** + * Replaces the element at the specified position in this array with the JSON representation of + * the specified boolean value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 or + * index >= size + */ + public JsonArray set( int index, boolean value ) { + values.set( index, valueOf( value ) ); + return this; + } + + /** + * Replaces the element at the specified position in this array with the JSON representation of + * the specified string. + * + * @param index + * the index of the array element to replace + * @param value + * the string to be stored at the specified array position + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 or + * index >= size + */ + public JsonArray set( int index, String value ) { + values.set( index, valueOf( value ) ); + return this; + } + + /** + * Replaces the element at the specified position in this array with the specified JSON value. + * + * @param index + * the index of the array element to replace + * @param value + * the value to be stored at the specified array position, must not be null + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 or + * index >= size + */ + public JsonArray set( int index, JsonValue value ) { + if( value == null ) { + throw new NullPointerException( "value is null" ); + } + values.set( index, value ); + return this; + } + + /** + * Removes the element at the specified index from this array. + * + * @param index + * the index of the element to remove + * @return the array itself, to enable method chaining + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 or + * index >= size + */ + public JsonArray remove( int index ) { + values.remove( index ); + return this; + } + + /** + * Returns the number of elements in this array. + * + * @return the number of elements in this array + */ + public int size() { + return values.size(); + } + + /** + * Returns true if this array contains no elements. + * + * @return true if this array contains no elements + */ + public boolean isEmpty() { + return values.isEmpty(); + } + + /** + * Returns the value of the element at the specified position in this array. + * + * @param index + * the index of the array element to return + * @return the value of the element at the specified position + * @throws IndexOutOfBoundsException + * if the index is out of range, i.e. index < 0 or + * index >= size + */ + public JsonValue get( int index ) { + return values.get( index ); + } + + /** + * Returns a list of the values in this array in document order. The returned list is backed by + * this array and will reflect subsequent changes. It cannot be used to modify this array. + * Attempts to modify the returned list will result in an exception. + * + * @return a list of the values in this array + */ + public List values() { + return Collections.unmodifiableList( values ); + } + + /** + * Returns an iterator over the values of this array in document order. The returned iterator + * cannot be used to modify this array. + * + * @return an iterator over the values of this array + */ + public Iterator iterator() { + final Iterator iterator = values.iterator(); + return new Iterator() { + + public boolean hasNext() { + return iterator.hasNext(); + } + + public JsonValue next() { + return iterator.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + protected void write( JsonWriter writer ) throws IOException { + writer.writeArray( this ); + } + + @Override + public boolean isArray() { + return true; + } + + @Override + public JsonArray asArray() { + return this; + } + + @Override + public int hashCode() { + return values.hashCode(); + } + + @Override + public boolean equals( Object object ) { + if( this == object ) { + return true; + } + if( object == null ) { + return false; + } + if( getClass() != object.getClass() ) { + return false; + } + JsonArray other = (JsonArray)object; + return values.equals( other.values ); + } + +} diff --git a/src/tk/cavariux/twitchirc/Json/JsonLiteral.java b/src/tk/cavariux/twitchirc/Json/JsonLiteral.java new file mode 100644 index 0000000..24a7dfd --- /dev/null +++ b/src/tk/cavariux/twitchirc/Json/JsonLiteral.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package tk.cavariux.twitchirc.Json; + +import java.io.IOException; + + +@SuppressWarnings( "serial" ) // use default serial UID +class JsonLiteral extends JsonValue { + + private final String value; + + JsonLiteral( String value ) { + this.value = value; + } + + @Override + protected void write( JsonWriter writer ) throws IOException { + writer.write( value ); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean asBoolean() { + return isBoolean() ? isTrue() : super.asBoolean(); + } + + @Override + public boolean isNull() { + return this == NULL; + } + + @Override + public boolean isBoolean() { + return this == TRUE || this == FALSE; + } + + @Override + public boolean isTrue() { + return this == TRUE; + } + + @Override + public boolean isFalse() { + return this == FALSE; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals( Object object ) { + if( this == object ) { + return true; + } + if( object == null ) { + return false; + } + if( getClass() != object.getClass() ) { + return false; + } + JsonLiteral other = (JsonLiteral)object; + return value.equals( other.value ); + } + +} diff --git a/src/tk/cavariux/twitchirc/Json/JsonNumber.java b/src/tk/cavariux/twitchirc/Json/JsonNumber.java new file mode 100644 index 0000000..364cf36 --- /dev/null +++ b/src/tk/cavariux/twitchirc/Json/JsonNumber.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package tk.cavariux.twitchirc.Json; + +import java.io.IOException; + + +@SuppressWarnings( "serial" ) // use default serial UID +class JsonNumber extends JsonValue { + + private final String string; + + JsonNumber( String string ) { + if( string == null ) { + throw new NullPointerException( "string is null" ); + } + this.string = string; + } + + @Override + public String toString() { + return string; + } + + @Override + protected void write( JsonWriter writer ) throws IOException { + writer.write( string ); + } + + @Override + public boolean isNumber() { + return true; + } + + @Override + public int asInt() { + return Integer.parseInt( string, 10 ); + } + + @Override + public long asLong() { + return Long.parseLong( string, 10 ); + } + + @Override + public float asFloat() { + return Float.parseFloat( string ); + } + + @Override + public double asDouble() { + return Double.parseDouble( string ); + } + + @Override + public int hashCode() { + return string.hashCode(); + } + + @Override + public boolean equals( Object object ) { + if( this == object ) { + return true; + } + if( object == null ) { + return false; + } + if( getClass() != object.getClass() ) { + return false; + } + JsonNumber other = (JsonNumber)object; + return string.equals( other.string ); + } + +} diff --git a/src/tk/cavariux/twitchirc/Json/JsonObject.java b/src/tk/cavariux/twitchirc/Json/JsonObject.java new file mode 100644 index 0000000..f902b4f --- /dev/null +++ b/src/tk/cavariux/twitchirc/Json/JsonObject.java @@ -0,0 +1,873 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package tk.cavariux.twitchirc.Json; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import tk.cavariux.twitchirc.Json.JsonObject.Member; + + +/** + * Represents a JSON object, i.e. an unordered set of name/value pairs, where the names are strings + * and the values are JSON values. + *

+ * Members can be added using the add(String, ...) methods which accept instances of + * {@link JsonValue}, strings, primitive numbers, and boolean values. To modify certain values of an + * object, use the set(String, ...) methods. Please note that the add + * methods are faster than set as they do not search for existing members. On the other + * hand, the add methods do not prevent adding multiple members with the same name. + * Duplicate names are discouraged but not prohibited by JSON. + *

+ *

+ * Members can be accessed by their name using {@link #get(String)}. A list of all names can be + * obtained from the method {@link #names()}. This class also supports iterating over the members in + * document order using an {@link #iterator()} or an enhanced for loop: + *

+ * + *
+ * for( Member member : jsonObject ) {
+ *   String name = member.getName();
+ *   JsonValue value = member.getValue();
+ *   ...
+ * }
+ * 
+ *

+ * Even though JSON objects are unordered by definition, instances of this class preserve the order + * of members to allow processing in document order and to guarantee a predictable output. + *

+ *

+ * Note that this class is not thread-safe. If multiple threads access a + * JsonObject instance concurrently, while at least one of these threads modifies the + * contents of this object, access to the instance must be synchronized externally. Failure to do so + * may lead to an inconsistent state. + *

+ *

+ * This class is not supposed to be extended by clients. + *

+ */ +@SuppressWarnings( "serial" ) +// use default serial UID +public class JsonObject extends JsonValue implements Iterable { + + private final List names; + private final List values; + private transient HashIndexTable table; + + /** + * Creates a new empty JsonObject. + */ + public JsonObject() { + names = new ArrayList(); + values = new ArrayList(); + table = new HashIndexTable(); + } + + /** + * Creates a new JsonObject, initialized with the contents of the specified JSON object. + * + * @param object + * the JSON object to get the initial contents from, must not be null + */ + public JsonObject( JsonObject object ) { + this( object, false ); + } + + private JsonObject( JsonObject object, boolean unmodifiable ) { + if( object == null ) { + throw new NullPointerException( "object is null" ); + } + if( unmodifiable ) { + names = Collections.unmodifiableList( object.names ); + values = Collections.unmodifiableList( object.values ); + } else { + names = new ArrayList( object.names ); + values = new ArrayList( object.values ); + } + table = new HashIndexTable(); + updateHashIndex(); + } + + /** + * Reads a JSON object from the given reader. + *

+ * Characters are read in chunks and buffered internally, therefore wrapping an existing reader in + * an additional BufferedReader does not improve reading + * performance. + *

+ * + * @param reader + * the reader to read the JSON object from + * @return the JSON object that has been read + * @throws IOException + * if an I/O error occurs in the reader + * @throws ParseException + * if the input is not valid JSON + * @throws UnsupportedOperationException + * if the input does not contain a JSON object + */ + public static JsonObject readFrom( Reader reader ) throws IOException { + return JsonValue.readFrom( reader ).asObject(); + } + + /** + * Reads a JSON object from the given string. + * + * @param string + * the string that contains the JSON object + * @return the JSON object that has been read + * @throws ParseException + * if the input is not valid JSON + * @throws UnsupportedOperationException + * if the input does not contain a JSON object + */ + public static JsonObject readFrom( String string ) { + return JsonValue.readFrom( string ).asObject(); + } + + /** + * Returns an unmodifiable JsonObject for the specified one. This method allows to provide + * read-only access to a JsonObject. + *

+ * The returned JsonObject is backed by the given object and reflect changes that happen to it. + * Attempts to modify the returned JsonObject result in an + * UnsupportedOperationException. + *

+ * + * @param object + * the JsonObject for which an unmodifiable JsonObject is to be returned + * @return an unmodifiable view of the specified JsonObject + */ + public static JsonObject unmodifiableObject( JsonObject object ) { + return new JsonObject( object, true ); + } + + /** + * Appends a new member to the end of this object, with the specified name and the JSON + * representation of the specified int value. + *

+ * This method does not prevent duplicate names. Calling this method with a name + * that already exists in the object will append another member with the same name. In order to + * replace existing members, use the method set(name, value) instead. However, + * add is much faster than set (because it does not need to + * search for existing members). Therefore add should be preferred when constructing new + * objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject add( String name, int value ) { + add( name, valueOf( value ) ); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name and the JSON + * representation of the specified long value. + *

+ * This method does not prevent duplicate names. Calling this method with a name + * that already exists in the object will append another member with the same name. In order to + * replace existing members, use the method set(name, value) instead. However, + * add is much faster than set (because it does not need to + * search for existing members). Therefore add should be preferred when constructing new + * objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject add( String name, long value ) { + add( name, valueOf( value ) ); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name and the JSON + * representation of the specified float value. + *

+ * This method does not prevent duplicate names. Calling this method with a name + * that already exists in the object will append another member with the same name. In order to + * replace existing members, use the method set(name, value) instead. However, + * add is much faster than set (because it does not need to + * search for existing members). Therefore add should be preferred when constructing new + * objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject add( String name, float value ) { + add( name, valueOf( value ) ); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name and the JSON + * representation of the specified double value. + *

+ * This method does not prevent duplicate names. Calling this method with a name + * that already exists in the object will append another member with the same name. In order to + * replace existing members, use the method set(name, value) instead. However, + * add is much faster than set (because it does not need to + * search for existing members). Therefore add should be preferred when constructing new + * objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject add( String name, double value ) { + add( name, valueOf( value ) ); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name and the JSON + * representation of the specified boolean value. + *

+ * This method does not prevent duplicate names. Calling this method with a name + * that already exists in the object will append another member with the same name. In order to + * replace existing members, use the method set(name, value) instead. However, + * add is much faster than set (because it does not need to + * search for existing members). Therefore add should be preferred when constructing new + * objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject add( String name, boolean value ) { + add( name, valueOf( value ) ); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name and the JSON + * representation of the specified string. + *

+ * This method does not prevent duplicate names. Calling this method with a name + * that already exists in the object will append another member with the same name. In order to + * replace existing members, use the method set(name, value) instead. However, + * add is much faster than set (because it does not need to + * search for existing members). Therefore add should be preferred when constructing new + * objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject add( String name, String value ) { + add( name, valueOf( value ) ); + return this; + } + + /** + * Appends a new member to the end of this object, with the specified name and the specified JSON + * value. + *

+ * This method does not prevent duplicate names. Calling this method with a name + * that already exists in the object will append another member with the same name. In order to + * replace existing members, use the method set(name, value) instead. However, + * add is much faster than set (because it does not need to + * search for existing members). Therefore add should be preferred when constructing new + * objects. + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add, must not be null + * @return the object itself, to enable method chaining + */ + public JsonObject add( String name, JsonValue value ) { + if( name == null ) { + throw new NullPointerException( "name is null" ); + } + if( value == null ) { + throw new NullPointerException( "value is null" ); + } + table.add( name, names.size() ); + names.add( name ); + values.add( value ); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON representation of the + * specified int value. If this object does not contain a member with this name, a + * new member is added at the end of the object. If this object contains multiple members with + * this name, only the last one is changed. + *

+ * This method should only be used to modify existing objects. To fill a new + * object with members, the method add(name, value) should be preferred which is much + * faster (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to replace + * @param value + * the value to set to the member + * @return the object itself, to enable method chaining + */ + public JsonObject set( String name, int value ) { + set( name, valueOf( value ) ); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON representation of the + * specified long value. If this object does not contain a member with this name, a + * new member is added at the end of the object. If this object contains multiple members with + * this name, only the last one is changed. + *

+ * This method should only be used to modify existing objects. To fill a new + * object with members, the method add(name, value) should be preferred which is much + * faster (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to replace + * @param value + * the value to set to the member + * @return the object itself, to enable method chaining + */ + public JsonObject set( String name, long value ) { + set( name, valueOf( value ) ); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON representation of the + * specified float value. If this object does not contain a member with this name, a + * new member is added at the end of the object. If this object contains multiple members with + * this name, only the last one is changed. + *

+ * This method should only be used to modify existing objects. To fill a new + * object with members, the method add(name, value) should be preferred which is much + * faster (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject set( String name, float value ) { + set( name, valueOf( value ) ); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON representation of the + * specified double value. If this object does not contain a member with this name, a + * new member is added at the end of the object. If this object contains multiple members with + * this name, only the last one is changed. + *

+ * This method should only be used to modify existing objects. To fill a new + * object with members, the method add(name, value) should be preferred which is much + * faster (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject set( String name, double value ) { + set( name, valueOf( value ) ); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON representation of the + * specified boolean value. If this object does not contain a member with this name, + * a new member is added at the end of the object. If this object contains multiple members with + * this name, only the last one is changed. + *

+ * This method should only be used to modify existing objects. To fill a new + * object with members, the method add(name, value) should be preferred which is much + * faster (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject set( String name, boolean value ) { + set( name, valueOf( value ) ); + return this; + } + + /** + * Sets the value of the member with the specified name to the JSON representation of the + * specified string. If this object does not contain a member with this name, a new member is + * added at the end of the object. If this object contains multiple members with this name, only + * the last one is changed. + *

+ * This method should only be used to modify existing objects. To fill a new + * object with members, the method add(name, value) should be preferred which is much + * faster (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add + * @return the object itself, to enable method chaining + */ + public JsonObject set( String name, String value ) { + set( name, valueOf( value ) ); + return this; + } + + /** + * Sets the value of the member with the specified name to the specified JSON value. If this + * object does not contain a member with this name, a new member is added at the end of the + * object. If this object contains multiple members with this name, only the last one is changed. + *

+ * This method should only be used to modify existing objects. To fill a new + * object with members, the method add(name, value) should be preferred which is much + * faster (as it does not need to search for existing members). + *

+ * + * @param name + * the name of the member to add + * @param value + * the value of the member to add, must not be null + * @return the object itself, to enable method chaining + */ + public JsonObject set( String name, JsonValue value ) { + if( name == null ) { + throw new NullPointerException( "name is null" ); + } + if( value == null ) { + throw new NullPointerException( "value is null" ); + } + int index = indexOf( name ); + if( index != -1 ) { + values.set( index, value ); + } else { + table.add( name, names.size() ); + names.add( name ); + values.add( value ); + } + return this; + } + + /** + * Removes a member with the specified name from this object. If this object contains multiple + * members with the given name, only the last one is removed. If this object does not contain a + * member with the specified name, the object is not modified. + * + * @param name + * the name of the member to remove + * @return the object itself, to enable method chaining + */ + public JsonObject remove( String name ) { + if( name == null ) { + throw new NullPointerException( "name is null" ); + } + int index = indexOf( name ); + if( index != -1 ) { + table.remove( index ); + names.remove( index ); + values.remove( index ); + } + return this; + } + + /** + * Returns the value of the member with the specified name in this object. If this object contains + * multiple members with the given name, this method will return the last one. + * + * @param name + * the name of the member whose value is to be returned + * @return the value of the last member with the specified name, or null if this + * object does not contain a member with that name + */ + public JsonValue get( String name ) { + if( name == null ) { + throw new NullPointerException( "name is null" ); + } + int index = indexOf( name ); + return index != -1 ? values.get( index ) : null; + } + + /** + * Returns the int value of the member with the specified name in this object. If + * this object does not contain a member with this name, the given default value is returned. If + * this object contains multiple members with the given name, the last one will be picked. If this + * member's value does not represent a JSON number or if it cannot be interpreted as Java + * int, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the given default value if + * this object does not contain a member with that name + */ + public int getInt( String name, int defaultValue ) { + JsonValue value = get( name ); + return value != null ? value.asInt() : defaultValue; + } + + /** + * Returns the long value of the member with the specified name in this object. If + * this object does not contain a member with this name, the given default value is returned. If + * this object contains multiple members with the given name, the last one will be picked. If this + * member's value does not represent a JSON number or if it cannot be interpreted as Java + * long, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the given default value if + * this object does not contain a member with that name + */ + public long getLong( String name, long defaultValue ) { + JsonValue value = get( name ); + return value != null ? value.asLong() : defaultValue; + } + + /** + * Returns the float value of the member with the specified name in this object. If + * this object does not contain a member with this name, the given default value is returned. If + * this object contains multiple members with the given name, the last one will be picked. If this + * member's value does not represent a JSON number or if it cannot be interpreted as Java + * float, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the given default value if + * this object does not contain a member with that name + */ + public float getFloat( String name, float defaultValue ) { + JsonValue value = get( name ); + return value != null ? value.asFloat() : defaultValue; + } + + /** + * Returns the double value of the member with the specified name in this object. If + * this object does not contain a member with this name, the given default value is returned. If + * this object contains multiple members with the given name, the last one will be picked. If this + * member's value does not represent a JSON number or if it cannot be interpreted as Java + * double, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the given default value if + * this object does not contain a member with that name + */ + public double getDouble( String name, double defaultValue ) { + JsonValue value = get( name ); + return value != null ? value.asDouble() : defaultValue; + } + + /** + * Returns the boolean value of the member with the specified name in this object. If + * this object does not contain a member with this name, the given default value is returned. If + * this object contains multiple members with the given name, the last one will be picked. If this + * member's value does not represent a JSON true or false value, an + * exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the given default value if + * this object does not contain a member with that name + */ + public boolean getBoolean( String name, boolean defaultValue ) { + JsonValue value = get( name ); + return value != null ? value.asBoolean() : defaultValue; + } + + /** + * Returns the String value of the member with the specified name in this object. If + * this object does not contain a member with this name, the given default value is returned. If + * this object contains multiple members with the given name, the last one is picked. If this + * member's value does not represent a JSON string, an exception is thrown. + * + * @param name + * the name of the member whose value is to be returned + * @param defaultValue + * the value to be returned if the requested member is missing + * @return the value of the last member with the specified name, or the given default value if + * this object does not contain a member with that name + */ + public String getString( String name, String defaultValue ) { + JsonValue value = get( name ); + return value != null ? value.asString() : defaultValue; + } + + /** + * Returns the number of members (i.e. name/value pairs) in this object. + * + * @return the number of members in this object + */ + public int size() { + return names.size(); + } + + /** + * Returns true if this object contains no members. + * + * @return true if this object contains no members + */ + public boolean isEmpty() { + return names.isEmpty(); + } + + /** + * Returns a list of the names in this object in document order. The returned list is backed by + * this object and will reflect subsequent changes. It cannot be used to modify this object. + * Attempts to modify the returned list will result in an exception. + * + * @returns a list of the names in this object + */ + public List names() { + return Collections.unmodifiableList( names ); + } + + /** + * Returns an iterator over the members of this object in document order. The returned iterator + * cannot be used to modify this object. + * + * @return an iterator over the members of this object + */ + public Iterator iterator() { + final Iterator namesIterator = names.iterator(); + final Iterator valuesIterator = values.iterator(); + return new Iterator() { + + public boolean hasNext() { + return namesIterator.hasNext(); + } + + public Member next() { + String name = namesIterator.next(); + JsonValue value = valuesIterator.next(); + return new Member( name, value ); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + + @Override + protected void write( JsonWriter writer ) throws IOException { + writer.writeObject( this ); + } + + @Override + public boolean isObject() { + return true; + } + + @Override + public JsonObject asObject() { + return this; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + names.hashCode(); + result = 31 * result + values.hashCode(); + return result; + } + + @Override + public boolean equals( Object obj ) { + if( this == obj ) { + return true; + } + if( obj == null ) { + return false; + } + if( getClass() != obj.getClass() ) { + return false; + } + JsonObject other = (JsonObject)obj; + return names.equals( other.names ) && values.equals( other.values ); + } + + int indexOf( String name ) { + int index = table.get( name ); + if( index != -1 && name.equals( names.get( index ) ) ) { + return index; + } + return names.lastIndexOf( name ); + } + + private synchronized void readObject( ObjectInputStream inputStream ) throws IOException, + ClassNotFoundException + { + inputStream.defaultReadObject(); + table = new HashIndexTable(); + updateHashIndex(); + } + + private void updateHashIndex() { + int size = names.size(); + for( int i = 0; i < size; i++ ) { + table.add( names.get( i ), i ); + } + } + + /** + * Represents a member of a JSON object, i.e. a pair of name and value. + */ + public static class Member { + + private final String name; + private final JsonValue value; + + Member( String name, JsonValue value ) { + this.name = name; + this.value = value; + } + + /** + * Returns the name of this member. + * + * @return the name of this member, never null + */ + public String getName() { + return name; + } + + /** + * Returns the value of this member. + * + * @return the value of this member, never null + */ + public JsonValue getValue() { + return value; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + name.hashCode(); + result = 31 * result + value.hashCode(); + return result; + } + + @Override + public boolean equals( Object obj ) { + if( this == obj ) { + return true; + } + if( obj == null ) { + return false; + } + if( getClass() != obj.getClass() ) { + return false; + } + Member other = (Member)obj; + return name.equals( other.name ) && value.equals( other.value ); + } + + } + + static class HashIndexTable { + + private final byte[] hashTable = new byte[32]; // must be a power of two + + public HashIndexTable() { + } + + public HashIndexTable( HashIndexTable original ) { + System.arraycopy( original.hashTable, 0, hashTable, 0, hashTable.length ); + } + + void add( String name, int index ) { + int slot = hashSlotFor( name ); + if( index < 0xff ) { + // increment by 1, 0 stands for empty + hashTable[slot] = (byte)( index + 1 ); + } else { + hashTable[slot] = 0; + } + } + + void remove( int index ) { + for( int i = 0; i < hashTable.length; i++ ) { + if( hashTable[i] == index + 1 ) { + hashTable[i] = 0; + } else if( hashTable[i] > index + 1 ) { + hashTable[i]--; + } + } + } + + int get( Object name ) { + int slot = hashSlotFor( name ); + // subtract 1, 0 stands for empty + return ( hashTable[slot] & 0xff ) - 1; + } + + private int hashSlotFor( Object element ) { + return element.hashCode() & hashTable.length - 1; + } + + } + +} diff --git a/src/tk/cavariux/twitchirc/Json/JsonParser.java b/src/tk/cavariux/twitchirc/Json/JsonParser.java new file mode 100644 index 0000000..6bbb558 --- /dev/null +++ b/src/tk/cavariux/twitchirc/Json/JsonParser.java @@ -0,0 +1,403 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package tk.cavariux.twitchirc.Json; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + + +class JsonParser { + + private static final int MIN_BUFFER_SIZE = 10; + private static final int DEFAULT_BUFFER_SIZE = 1024; + + private final Reader reader; + private final char[] buffer; + private int bufferOffset; + private int index; + private int fill; + private int line; + private int lineOffset; + private int current; + private StringBuilder captureBuffer; + private int captureStart; + + /* + * | bufferOffset + * v + * [a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t] < input + * [l|m|n|o|p|q|r|s|t|?|?] < buffer + * ^ ^ + * | index fill + */ + + JsonParser( String string ) { + this( new StringReader( string ), + Math.max( MIN_BUFFER_SIZE, Math.min( DEFAULT_BUFFER_SIZE, string.length() ) ) ); + } + + JsonParser( Reader reader ) { + this( reader, DEFAULT_BUFFER_SIZE ); + } + + JsonParser( Reader reader, int buffersize ) { + this.reader = reader; + buffer = new char[ buffersize ]; + line = 1; + captureStart = -1; + } + + JsonValue parse() throws IOException { + read(); + skipWhiteSpace(); + JsonValue result = readValue(); + skipWhiteSpace(); + if( !isEndOfText() ) { + throw error( "Unexpected character" ); + } + return result; + } + + private JsonValue readValue() throws IOException { + switch( current ) { + case 'n': + return readNull(); + case 't': + return readTrue(); + case 'f': + return readFalse(); + case '"': + return readString(); + case '[': + return readArray(); + case '{': + return readObject(); + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return readNumber(); + default: + throw expected( "value" ); + } + } + + private JsonArray readArray() throws IOException { + read(); + JsonArray array = new JsonArray(); + skipWhiteSpace(); + if( readChar( ']' ) ) { + return array; + } + do { + skipWhiteSpace(); + array.add( readValue() ); + skipWhiteSpace(); + } while( readChar( ',' ) ); + if( !readChar( ']' ) ) { + throw expected( "',' or ']'" ); + } + return array; + } + + private JsonObject readObject() throws IOException { + read(); + JsonObject object = new JsonObject(); + skipWhiteSpace(); + if( readChar( '}' ) ) { + return object; + } + do { + skipWhiteSpace(); + String name = readName(); + skipWhiteSpace(); + if( !readChar( ':' ) ) { + throw expected( "':'" ); + } + skipWhiteSpace(); + object.add( name, readValue() ); + skipWhiteSpace(); + } while( readChar( ',' ) ); + if( !readChar( '}' ) ) { + throw expected( "',' or '}'" ); + } + return object; + } + + private String readName() throws IOException { + if( current != '"' ) { + throw expected( "name" ); + } + return readStringInternal(); + } + + private JsonValue readNull() throws IOException { + read(); + readRequiredChar( 'u' ); + readRequiredChar( 'l' ); + readRequiredChar( 'l' ); + return JsonValue.NULL; + } + + private JsonValue readTrue() throws IOException { + read(); + readRequiredChar( 'r' ); + readRequiredChar( 'u' ); + readRequiredChar( 'e' ); + return JsonValue.TRUE; + } + + private JsonValue readFalse() throws IOException { + read(); + readRequiredChar( 'a' ); + readRequiredChar( 'l' ); + readRequiredChar( 's' ); + readRequiredChar( 'e' ); + return JsonValue.FALSE; + } + + private void readRequiredChar( char ch ) throws IOException { + if( !readChar( ch ) ) { + throw expected( "'" + ch + "'" ); + } + } + + private JsonValue readString() throws IOException { + return new JsonString( readStringInternal() ); + } + + private String readStringInternal() throws IOException { + read(); + startCapture(); + while( current != '"' ) { + if( current == '\\' ) { + pauseCapture(); + readEscape(); + startCapture(); + } else if( current < 0x20 ) { + throw expected( "valid string character" ); + } else { + read(); + } + } + String string = endCapture(); + read(); + return string; + } + + private void readEscape() throws IOException { + read(); + switch( current ) { + case '"': + case '/': + case '\\': + captureBuffer.append( (char)current ); + break; + case 'b': + captureBuffer.append( '\b' ); + break; + case 'f': + captureBuffer.append( '\f' ); + break; + case 'n': + captureBuffer.append( '\n' ); + break; + case 'r': + captureBuffer.append( '\r' ); + break; + case 't': + captureBuffer.append( '\t' ); + break; + case 'u': + char[] hexChars = new char[4]; + for( int i = 0; i < 4; i++ ) { + read(); + if( !isHexDigit() ) { + throw expected( "hexadecimal digit" ); + } + hexChars[i] = (char)current; + } + captureBuffer.append( (char)Integer.parseInt( String.valueOf( hexChars ), 16 ) ); + break; + default: + throw expected( "valid escape sequence" ); + } + read(); + } + + private JsonValue readNumber() throws IOException { + startCapture(); + readChar( '-' ); + int firstDigit = current; + if( !readDigit() ) { + throw expected( "digit" ); + } + if( firstDigit != '0' ) { + while( readDigit() ) { + } + } + readFraction(); + readExponent(); + return new JsonNumber( endCapture() ); + } + + private boolean readFraction() throws IOException { + if( !readChar( '.' ) ) { + return false; + } + if( !readDigit() ) { + throw expected( "digit" ); + } + while( readDigit() ) { + } + return true; + } + + private boolean readExponent() throws IOException { + if( !readChar( 'e' ) && !readChar( 'E' ) ) { + return false; + } + if( !readChar( '+' ) ) { + readChar( '-' ); + } + if( !readDigit() ) { + throw expected( "digit" ); + } + while( readDigit() ) { + } + return true; + } + + private boolean readChar( char ch ) throws IOException { + if( current != ch ) { + return false; + } + read(); + return true; + } + + private boolean readDigit() throws IOException { + if( !isDigit() ) { + return false; + } + read(); + return true; + } + + private void skipWhiteSpace() throws IOException { + while( isWhiteSpace() ) { + read(); + } + } + + private void read() throws IOException { + if( isEndOfText() ) { + throw error( "Unexpected end of input" ); + } + if( index == fill ) { + if( captureStart != -1 ) { + captureBuffer.append( buffer, captureStart, fill - captureStart ); + captureStart = 0; + } + bufferOffset += fill; + fill = reader.read( buffer, 0, buffer.length ); + index = 0; + if( fill == -1 ) { + current = -1; + return; + } + } + if( current == '\n' ) { + line++; + lineOffset = bufferOffset + index; + } + current = buffer[index++]; + } + + private void startCapture() { + if( captureBuffer == null ) { + captureBuffer = new StringBuilder(); + } + captureStart = index - 1; + } + + private void pauseCapture() { + int end = current == -1 ? index : index - 1; + captureBuffer.append( buffer, captureStart, end - captureStart ); + captureStart = -1; + } + + private String endCapture() { + int end = current == -1 ? index : index - 1; + String captured; + if( captureBuffer.length() > 0 ) { + captureBuffer.append( buffer, captureStart, end - captureStart ); + captured = captureBuffer.toString(); + captureBuffer.setLength( 0 ); + } else { + captured = new String( buffer, captureStart, end - captureStart ); + } + captureStart = -1; + return captured; + } + + private ParseException expected( String expected ) { + if( isEndOfText() ) { + return error( "Unexpected end of input" ); + } + return error( "Expected " + expected ); + } + + private ParseException error( String message ) { + int absIndex = bufferOffset + index; + int column = absIndex - lineOffset; + int offset = isEndOfText() ? absIndex : absIndex - 1; + return new ParseException( message, offset, line, column - 1 ); + } + + private boolean isWhiteSpace() { + return current == ' ' || current == '\t' || current == '\n' || current == '\r'; + } + + private boolean isDigit() { + return current >= '0' && current <= '9'; + } + + private boolean isHexDigit() { + return current >= '0' && current <= '9' + || current >= 'a' && current <= 'f' + || current >= 'A' && current <= 'F'; + } + + private boolean isEndOfText() { + return current == -1; + } + +} diff --git a/src/tk/cavariux/twitchirc/Json/JsonString.java b/src/tk/cavariux/twitchirc/Json/JsonString.java new file mode 100644 index 0000000..521c180 --- /dev/null +++ b/src/tk/cavariux/twitchirc/Json/JsonString.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package tk.cavariux.twitchirc.Json; + +import java.io.IOException; + + +@SuppressWarnings( "serial" ) // use default serial UID +class JsonString extends JsonValue { + + private final String string; + + JsonString( String string ) { + if( string == null ) { + throw new NullPointerException( "string is null" ); + } + this.string = string; + } + + @Override + protected void write( JsonWriter writer ) throws IOException { + writer.writeString( string ); + } + + @Override + public boolean isString() { + return true; + } + + @Override + public String asString() { + return string; + } + + @Override + public int hashCode() { + return string.hashCode(); + } + + @Override + public boolean equals( Object object ) { + if( this == object ) { + return true; + } + if( object == null ) { + return false; + } + if( getClass() != object.getClass() ) { + return false; + } + JsonString other = (JsonString)object; + return string.equals( other.string ); + } + +} diff --git a/src/tk/cavariux/twitchirc/Json/JsonValue.java b/src/tk/cavariux/twitchirc/Json/JsonValue.java new file mode 100644 index 0000000..04f6672 --- /dev/null +++ b/src/tk/cavariux/twitchirc/Json/JsonValue.java @@ -0,0 +1,460 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package tk.cavariux.twitchirc.Json; + +import java.io.IOException; +import java.io.Reader; +import java.io.Serializable; +import java.io.StringWriter; +import java.io.Writer; + + +/** + * Represents a JSON value. This can be a JSON object, an array, + * a number, a string, or one of the literals + * true, false, and null. + *

+ * The literals true, false, and null are + * represented by the constants {@link #TRUE}, {@link #FALSE}, and {@link #NULL}. + *

+ *

+ * JSON objects and arrays are represented by the subtypes + * {@link JsonObject} and {@link JsonArray}. Instances of these types can be created using the + * public constructors of these classes. + *

+ *

+ * Instances that represent JSON numbers, strings and + * boolean values can be created using the static factory methods + * {@link #valueOf(String)}, {@link #valueOf(long)}, {@link #valueOf(double)}, etc. + *

+ *

+ * In order to find out whether an instance of this class is of a certain type, the methods + * {@link #isObject()}, {@link #isArray()}, {@link #isString()}, {@link #isNumber()} etc. can be + * used. + *

+ *

+ * If the type of a JSON value is known, the methods {@link #asObject()}, {@link #asArray()}, + * {@link #asString()}, {@link #asInt()}, etc. can be used to get this value directly in the + * appropriate target type. + *

+ *

+ * This class is not supposed to be extended by clients. + *

+ */ +@SuppressWarnings( "serial" ) // use default serial UID +public abstract class JsonValue implements Serializable { + + /** + * Represents the JSON literal true. + */ + public static final JsonValue TRUE = new JsonLiteral( "true" ); + + /** + * Represents the JSON literal false. + */ + public static final JsonValue FALSE = new JsonLiteral( "false" ); + + /** + * The JSON literal null. + */ + public static final JsonValue NULL = new JsonLiteral( "null" ); + + JsonValue() { + // prevent subclasses outside of this package + } + + /** + * Reads a JSON value from the given reader. + *

+ * Characters are read in chunks and buffered internally, therefore wrapping an existing reader in + * an additional BufferedReader does not improve reading + * performance. + *

+ * + * @param reader + * the reader to read the JSON value from + * @return the JSON value that has been read + * @throws IOException + * if an I/O error occurs in the reader + * @throws ParseException + * if the input is not valid JSON + */ + public static JsonValue readFrom( Reader reader ) throws IOException { + return new JsonParser( reader ).parse(); + } + + /** + * Reads a JSON value from the given string. + * + * @param text + * the string that contains the JSON value + * @return the JSON value that has been read + * @throws ParseException + * if the input is not valid JSON + */ + public static JsonValue readFrom( String text ) { + try { + return new JsonParser( text ).parse(); + } catch( IOException exception ) { + // JsonParser does not throw IOException for String + throw new RuntimeException( exception ); + } + } + + /** + * Returns a JsonValue instance that represents the given int value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JsonValue valueOf( int value ) { + return new JsonNumber( Integer.toString( value, 10 ) ); + } + + /** + * Returns a JsonValue instance that represents the given long value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JsonValue valueOf( long value ) { + return new JsonNumber( Long.toString( value, 10 ) ); + } + + /** + * Returns a JsonValue instance that represents the given float value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JsonValue valueOf( float value ) { + if( Float.isInfinite( value ) || Float.isNaN( value ) ) { + throw new IllegalArgumentException( "Infinite and NaN values not permitted in JSON" ); + } + return new JsonNumber( cutOffPointZero( Float.toString( value ) ) ); + } + + /** + * Returns a JsonValue instance that represents the given double value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JsonValue valueOf( double value ) { + if( Double.isInfinite( value ) || Double.isNaN( value ) ) { + throw new IllegalArgumentException( "Infinite and NaN values not permitted in JSON" ); + } + return new JsonNumber( cutOffPointZero( Double.toString( value ) ) ); + } + + /** + * Returns a JsonValue instance that represents the given string. + * + * @param string + * the string to get a JSON representation for + * @return a JSON value that represents the given string + */ + public static JsonValue valueOf( String string ) { + return string == null ? NULL : new JsonString( string ); + } + + /** + * Returns a JsonValue instance that represents the given boolean value. + * + * @param value + * the value to get a JSON representation for + * @return a JSON value that represents the given value + */ + public static JsonValue valueOf( boolean value ) { + return value ? TRUE : FALSE; + } + + /** + * Detects whether this value represents a JSON object. If this is the case, this value is an + * instance of {@link JsonObject}. + * + * @return true if this value is an instance of JsonObject + */ + public boolean isObject() { + return false; + } + + /** + * Detects whether this value represents a JSON array. If this is the case, this value is an + * instance of {@link JsonArray}. + * + * @return true if this value is an instance of JsonArray + */ + public boolean isArray() { + return false; + } + + /** + * Detects whether this value represents a JSON number. + * + * @return true if this value represents a JSON number + */ + public boolean isNumber() { + return false; + } + + /** + * Detects whether this value represents a JSON string. + * + * @return true if this value represents a JSON string + */ + public boolean isString() { + return false; + } + + /** + * Detects whether this value represents a boolean value. + * + * @return true if this value represents either the JSON literal + * true or false + */ + public boolean isBoolean() { + return false; + } + + /** + * Detects whether this value represents the JSON literal true. + * + * @return true if this value represents the JSON literal + * true + */ + public boolean isTrue() { + return false; + } + + /** + * Detects whether this value represents the JSON literal false. + * + * @return true if this value represents the JSON literal + * false + */ + public boolean isFalse() { + return false; + } + + /** + * Detects whether this value represents the JSON literal null. + * + * @return true if this value represents the JSON literal + * null + */ + public boolean isNull() { + return false; + } + + /** + * Returns this JSON value as {@link JsonObject}, assuming that this value represents a JSON + * object. If this is not the case, an exception is thrown. + * + * @return a JSONObject for this value + * @throws UnsupportedOperationException + * if this value is not a JSON object + */ + public JsonObject asObject() { + throw new UnsupportedOperationException( "Not an object: " + toString() ); + } + + /** + * Returns this JSON value as {@link JsonArray}, assuming that this value represents a JSON + * array. If this is not the case, an exception is thrown. + * + * @return a JSONArray for this value + * @throws UnsupportedOperationException + * if this value is not a JSON array + */ + public JsonArray asArray() { + throw new UnsupportedOperationException( "Not an array: " + toString() ); + } + + /** + * Returns this JSON value as an int value, assuming that this value represents a + * JSON number that can be interpreted as Java int. If this is not the case, an + * exception is thrown. + *

+ * To be interpreted as Java int, the JSON number must neither contain an exponent + * nor a fraction part. Moreover, the number must be in the Integer range. + *

+ * + * @return this value as int + * @throws UnsupportedOperationException + * if this value is not a JSON number + * @throws NumberFormatException + * if this JSON number can not be interpreted as int value + */ + public int asInt() { + throw new UnsupportedOperationException( "Not a number: " + toString() ); + } + + /** + * Returns this JSON value as a long value, assuming that this value represents a + * JSON number that can be interpreted as Java long. If this is not the case, an + * exception is thrown. + *

+ * To be interpreted as Java long, the JSON number must neither contain an exponent + * nor a fraction part. Moreover, the number must be in the Long range. + *

+ * + * @return this value as long + * @throws UnsupportedOperationException + * if this value is not a JSON number + * @throws NumberFormatException + * if this JSON number can not be interpreted as long value + */ + public long asLong() { + throw new UnsupportedOperationException( "Not a number: " + toString() ); + } + + /** + * Returns this JSON value as a float value, assuming that this value represents a + * JSON number. If this is not the case, an exception is thrown. + *

+ * If the JSON number is out of the Float range, {@link Float#POSITIVE_INFINITY} or + * {@link Float#NEGATIVE_INFINITY} is returned. + *

+ * + * @return this value as float + * @throws UnsupportedOperationException + * if this value is not a JSON number + */ + public float asFloat() { + throw new UnsupportedOperationException( "Not a number: " + toString() ); + } + + /** + * Returns this JSON value as a double value, assuming that this value represents a + * JSON number. If this is not the case, an exception is thrown. + *

+ * If the JSON number is out of the Double range, {@link Double#POSITIVE_INFINITY} or + * {@link Double#NEGATIVE_INFINITY} is returned. + *

+ * + * @return this value as double + * @throws UnsupportedOperationException + * if this value is not a JSON number + */ + public double asDouble() { + throw new UnsupportedOperationException( "Not a number: " + toString() ); + } + + /** + * Returns this JSON value as String, assuming that this value represents a JSON string. If this + * is not the case, an exception is thrown. + * + * @return the string represented by this value + * @throws UnsupportedOperationException + * if this value is not a JSON string + */ + public String asString() { + throw new UnsupportedOperationException( "Not a string: " + toString() ); + } + + /** + * Returns this JSON value as a boolean value, assuming that this value is either + * true or false. If this is not the case, an exception is thrown. + * + * @return this value as boolean + * @throws UnsupportedOperationException + * if this value is neither true or false + */ + public boolean asBoolean() { + throw new UnsupportedOperationException( "Not a boolean: " + toString() ); + } + + /** + * Writes the JSON representation for this object to the given writer. + *

+ * Single elements are passed directly to the given writer. Therefore, if the writer is not + * buffered, wrapping it in a {@link java.io.BufferedWriter BufferedWriter} can drastically + * improve writing performance. + *

+ * + * @param writer + * the writer to write this value to + * @throws IOException + * if an I/O error occurs in the writer + */ + public void writeTo( Writer writer ) throws IOException { + write( new JsonWriter( writer ) ); + } + + /** + * Returns the JSON string for this value in its minimal form, without any additional whitespace. + * The result is guaranteed to be a valid input for the method {@link #readFrom(String)} and to + * create a value that is equal to this object. + * + * @return a JSON string that represents this value + */ + @Override + public String toString() { + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter( stringWriter ); + try { + write( jsonWriter ); + } catch( IOException exception ) { + // StringWriter does not throw IOExceptions + throw new RuntimeException( exception ); + } + return stringWriter.toString(); + } + + /** + * Indicates whether some other object is "equal to" this one according to the contract specified + * in {@link Object#equals(Object)}. + *

+ * Two JsonValues are considered equal if and only if they represent the same JSON text. As a + * consequence, two given JsonObjects may be different even though they contain the same set of + * names with the same values, but in a different order. + *

+ * + * @param object + * the reference object with which to compare + * @return true if this object is the same as the object argument; false otherwise + */ + @Override + public boolean equals( Object object ) { + return super.equals( object ); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + protected abstract void write( JsonWriter writer ) throws IOException; + + private static String cutOffPointZero( String string ) { + if( string.endsWith( ".0" ) ) { + return string.substring( 0, string.length() - 2 ); + } + return string; + } + +} diff --git a/src/tk/cavariux/twitchirc/Json/JsonWriter.java b/src/tk/cavariux/twitchirc/Json/JsonWriter.java new file mode 100644 index 0000000..9684258 --- /dev/null +++ b/src/tk/cavariux/twitchirc/Json/JsonWriter.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package tk.cavariux.twitchirc.Json; + +import java.io.IOException; +import java.io.Writer; + + +class JsonWriter { + + private static final int CONTROL_CHARACTERS_START = 0x0000; + private static final int CONTROL_CHARACTERS_END = 0x001f; + + private static final char[] QUOT_CHARS = { '\\', '"' }; + private static final char[] BS_CHARS = { '\\', '\\' }; + private static final char[] LF_CHARS = { '\\', 'n' }; + private static final char[] CR_CHARS = { '\\', 'r' }; + private static final char[] TAB_CHARS = { '\\', 't' }; + // In JavaScript, U+2028 and U+2029 characters count as line endings and must be encoded. + // http://stackoverflow.com/questions/2965293/javascript-parse-error-on-u2028-unicode-character + private static final char[] UNICODE_2028_CHARS = { '\\', 'u', '2', '0', '2', '8' }; + private static final char[] UNICODE_2029_CHARS = { '\\', 'u', '2', '0', '2', '9' }; + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f' }; + + protected final Writer writer; + + JsonWriter( Writer writer ) { + this.writer = writer; + } + + void write( String string ) throws IOException { + writer.write( string ); + } + + void writeString( String string ) throws IOException { + writer.write( '"' ); + int length = string.length(); + int start = 0; + char[] chars = new char[ length ]; + string.getChars( 0, length, chars, 0 ); + for( int index = 0; index < length; index++ ) { + char[] replacement = getReplacementChars( chars[index] ); + if( replacement != null ) { + writer.write( chars, start, index - start ); + writer.write( replacement ); + start = index+1; + } + } + writer.write( chars, start, length - start ); + writer.write( '"' ); + } + + private static char[] getReplacementChars( char ch ) { + char[] replacement = null; + if( ch == '"' ) { + replacement = QUOT_CHARS; + } else if( ch == '\\' ) { + replacement = BS_CHARS; + } else if( ch == '\n' ) { + replacement = LF_CHARS; + } else if( ch == '\r' ) { + replacement = CR_CHARS; + } else if( ch == '\t' ) { + replacement = TAB_CHARS; + } else if( ch == '\u2028' ) { + replacement = UNICODE_2028_CHARS; + } else if( ch == '\u2029' ) { + replacement = UNICODE_2029_CHARS; + } else if( ch >= CONTROL_CHARACTERS_START && ch <= CONTROL_CHARACTERS_END ) { + replacement = new char[] { '\\', 'u', '0', '0', '0', '0' }; + replacement[4] = HEX_DIGITS[ ch >> 4 & 0x000f ]; + replacement[5] = HEX_DIGITS[ ch & 0x000f ]; + } + return replacement; + } + + protected void writeObject( JsonObject object ) throws IOException { + writeBeginObject(); + boolean first = true; + for( JsonObject.Member member : object ) { + if( !first ) { + writeObjectValueSeparator(); + } + writeString( member.getName() ); + writeNameValueSeparator(); + member.getValue().write( this ); + first = false; + } + writeEndObject(); + } + + protected void writeBeginObject() throws IOException { + writer.write( '{' ); + } + + protected void writeEndObject() throws IOException { + writer.write( '}' ); + } + + protected void writeNameValueSeparator() throws IOException { + writer.write( ':' ); + } + + protected void writeObjectValueSeparator() throws IOException { + writer.write( ',' ); + } + + protected void writeArray( JsonArray array ) throws IOException { + writeBeginArray(); + boolean first = true; + for( JsonValue value : array ) { + if( !first ) { + writeArrayValueSeparator(); + } + value.write( this ); + first = false; + } + writeEndArray(); + } + + protected void writeBeginArray() throws IOException { + writer.write( '[' ); + } + + protected void writeEndArray() throws IOException { + writer.write( ']' ); + } + + protected void writeArrayValueSeparator() throws IOException { + writer.write( ',' ); + } + +} diff --git a/src/tk/cavariux/twitchirc/Json/ParseException.java b/src/tk/cavariux/twitchirc/Json/ParseException.java new file mode 100644 index 0000000..98dfbc9 --- /dev/null +++ b/src/tk/cavariux/twitchirc/Json/ParseException.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2013, 2014 EclipseSource. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + ******************************************************************************/ +package tk.cavariux.twitchirc.Json; + + +/** + * An unchecked exception to indicate that an input does not qualify as valid JSON. + */ +@SuppressWarnings( "serial" ) // use default serial UID +public class ParseException extends RuntimeException { + + private final int offset; + private final int line; + private final int column; + + ParseException( String message, int offset, int line, int column ) { + super( message + " at " + line + ":" + column ); + this.offset = offset; + this.line = line; + this.column = column; + } + + /** + * Returns the absolute index of the character at which the error occurred. The + * index of the first character of a document is 0. + * + * @return the character offset at which the error occurred, will be >= 0 + */ + public int getOffset() { + return offset; + } + + /** + * Returns the number of the line in which the error occurred. The first line counts as 1. + * + * @return the line in which the error occurred, will be >= 1 + */ + public int getLine() { + return line; + } + + /** + * Returns the index of the character at which the error occurred, relative to the line. The + * index of the first character of a line is 0. + * + * @return the column in which the error occurred, will be >= 0 + */ + public int getColumn() { + return column; + } + +}