Initial code merge
This commit is contained in:
183
pom.xml
Normal file
183
pom.xml
Normal file
@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>io.teknek</groupId>
|
||||
<artifactId>gossip</artifactId>
|
||||
<name>gossip</name>
|
||||
<version>0.0.0-SNAPSHOT</version>
|
||||
<description>A peer to peer cluster process</description>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
<artifactId>oss-parent</artifactId>
|
||||
<version>7</version>
|
||||
</parent>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/edwardcapriolo/teknek-core</url>
|
||||
<connection>https://github.com/edwardcapriolo/teknek-core.git</connection>
|
||||
<!--<developerConnection>scm:git:https://github.com/edwardcapriolo/teknek-core.git</developerConnection> -->
|
||||
<!--<developerConnection>scm:git:git@github.com:juven/git-demo.git</developerConnection> -->
|
||||
<developerConnection>scm:git:git@github.com:edwardcapriolo/teknek-core.git</developerConnection>
|
||||
</scm>
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.8.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
<version>1.2.15</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>javax.jms</groupId>
|
||||
<artifactId>jms</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.sun.jdmk</groupId>
|
||||
<artifactId>jmxtools</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.sun.jmx</groupId>
|
||||
<artifactId>jmxri</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId>
|
||||
<version>2.1</version> <configuration> <mavenExecutorId>forked-path</mavenExecutorId>
|
||||
<useReleaseProfile>false</useReleaseProfile> <arguments>${arguments} -Psonatype-oss-release</arguments>
|
||||
</configuration> </plugin> -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.2</version>
|
||||
<configuration>
|
||||
</configuration>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-eclipse-plugin</artifactId>
|
||||
<version>2.5.1</version>
|
||||
<configuration>
|
||||
<projectNameTemplate>[artifactId]</projectNameTemplate>
|
||||
<wtpmanifest>true</wtpmanifest>
|
||||
<wtpapplicationxml>true</wtpapplicationxml>
|
||||
<wtpversion>1.5</wtpversion>
|
||||
<additionalBuildcommands>
|
||||
<buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
|
||||
<buildcommand>org.maven.ide.eclipse.maven2Builder</buildcommand>
|
||||
</additionalBuildcommands>
|
||||
<additionalProjectnatures>
|
||||
<projectnature>org.eclipse.jdt.core.javanature</projectnature>
|
||||
<projectnature>org.maven.ide.eclipse.maven2Nature</projectnature>
|
||||
</additionalProjectnatures>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
<repositories>
|
||||
</repositories>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>ecapriolo</id>
|
||||
<name>Edward Capriolo</name>
|
||||
<email>edlinuxguru@gmail.com</email>
|
||||
<url />
|
||||
<organization />
|
||||
<organizationUrl />
|
||||
<roles>
|
||||
<role>developer</role>
|
||||
</roles>
|
||||
<timezone>-6</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>performRelease</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
138
src/main/java/com/google/code/gossip/GossipMember.java
Normal file
138
src/main/java/com/google/code/gossip/GossipMember.java
Normal file
@ -0,0 +1,138 @@
|
||||
package com.google.code.gossip;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* A abstract class representing a gossip member.
|
||||
*
|
||||
* @author joshclemm, harmenw
|
||||
*/
|
||||
public abstract class GossipMember {
|
||||
/** The JSON key for the host property. */
|
||||
public static final String JSON_HOST = "host";
|
||||
/** The JSON key for the port property. */
|
||||
public static final String JSON_PORT = "port";
|
||||
/** The JSON key for the heartbeat property. */
|
||||
public static final String JSON_HEARTBEAT = "heartbeat";
|
||||
|
||||
/** The hostname or IP address of this gossip member. */
|
||||
protected String _host;
|
||||
|
||||
/** The port number of this gossip member. */
|
||||
protected int _port;
|
||||
|
||||
/** The current heartbeat of this gossip member. */
|
||||
protected int _heartbeat;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param host The hostname or IP address.
|
||||
* @param port The port number.
|
||||
* @param heartbeat The current heartbeat.
|
||||
*/
|
||||
public GossipMember(String host, int port, int heartbeat) {
|
||||
_host = host;
|
||||
_port = port;
|
||||
_heartbeat = heartbeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hostname or IP address of the remote gossip member.
|
||||
* @return The hostname or IP address.
|
||||
*/
|
||||
public String getHost() {
|
||||
return _host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port number of the remote gossip member.
|
||||
* @return The port number.
|
||||
*/
|
||||
public int getPort() {
|
||||
return _port;
|
||||
}
|
||||
|
||||
/**
|
||||
* The member address in the form IP/host:port
|
||||
* Similar to the toString in {@link InetSocketAddress}
|
||||
*/
|
||||
public String getAddress() {
|
||||
return _host+":"+_port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the heartbeat of this gossip member.
|
||||
* @return The current heartbeat.
|
||||
*/
|
||||
public int getHeartbeat() {
|
||||
return _heartbeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the heartbeat of this gossip member.
|
||||
* @param heartbeat The new heartbeat.
|
||||
*/
|
||||
public void setHeartbeat(int heartbeat) {
|
||||
this._heartbeat = heartbeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return "Member [address=" + getAddress() + ", heartbeat=" + _heartbeat + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
String address = getAddress();
|
||||
result = prime * result
|
||||
+ ((address == null) ? 0 : address.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
System.err.println("equals(): obj is null.");
|
||||
return false;
|
||||
}
|
||||
if (! (obj instanceof GossipMember) ) {
|
||||
System.err.println("equals(): obj is not of type GossipMember.");
|
||||
return false;
|
||||
}
|
||||
// The object is the same of they both have the same address (hostname and port).
|
||||
return getAddress().equals(((LocalGossipMember) obj).getAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSONObject which is the JSON representation of this GossipMember.
|
||||
* @return The JSONObject of this GossipMember.
|
||||
*/
|
||||
public JSONObject toJSONObject() {
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put(JSON_HOST, _host);
|
||||
jsonObject.put(JSON_PORT, _port);
|
||||
jsonObject.put(JSON_HEARTBEAT, _heartbeat);
|
||||
return jsonObject;
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
46
src/main/java/com/google/code/gossip/GossipRunner.java
Normal file
46
src/main/java/com/google/code/gossip/GossipRunner.java
Normal file
@ -0,0 +1,46 @@
|
||||
package com.google.code.gossip;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
public class GossipRunner {
|
||||
private StartupSettings _settings;
|
||||
|
||||
public static void main(String[] args) {
|
||||
File configFile;
|
||||
|
||||
if (args.length == 1) {
|
||||
configFile = new File("./"+args[0]);
|
||||
} else {
|
||||
configFile = new File("gossip.conf");
|
||||
}
|
||||
|
||||
new GossipRunner(configFile);
|
||||
}
|
||||
|
||||
public GossipRunner(File configFile) {
|
||||
|
||||
if (configFile != null && configFile.exists()) {
|
||||
try {
|
||||
System.out.println("Parsing the configuration file...");
|
||||
_settings = StartupSettings.fromJSONFile(configFile);
|
||||
GossipService gossipService = new GossipService(_settings);
|
||||
System.out.println("Gossip service successfully inialized, let's start it...");
|
||||
gossipService.start();
|
||||
} catch (FileNotFoundException e) {
|
||||
System.err.println("The given file is not found!");
|
||||
} catch (JSONException e) {
|
||||
System.err.println("The given file is not in the correct JSON format!");
|
||||
} catch (IOException e) {
|
||||
System.err.println("Could not read the configuration file: " + e.getMessage());
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Error while starting the gossip service: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
System.out.println("The gossip.conf file is not found.\n\nEither specify the path to the startup settings file or place the gossip.json file in the same folder as the JAR file.");
|
||||
}
|
||||
}
|
||||
}
|
77
src/main/java/com/google/code/gossip/GossipService.java
Normal file
77
src/main/java/com/google/code/gossip/GossipService.java
Normal file
@ -0,0 +1,77 @@
|
||||
package com.google.code.gossip;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import com.google.code.gossip.manager.GossipManager;
|
||||
import com.google.code.gossip.manager.random.RandomGossipManager;
|
||||
|
||||
/**
|
||||
* This object represents the service which is responsible for gossiping with other gossip members.
|
||||
*
|
||||
* @author joshclemm, harmenw
|
||||
*/
|
||||
public class GossipService {
|
||||
|
||||
/** A instance variable holding the log level. */
|
||||
private int _logLevel = LogLevel.INFO;
|
||||
|
||||
private GossipManager _gossipManager;
|
||||
|
||||
/**
|
||||
* Constructor with the default settings.
|
||||
* @throws InterruptedException
|
||||
* @throws UnknownHostException
|
||||
*/
|
||||
public GossipService(StartupSettings startupSettings) throws InterruptedException, UnknownHostException {
|
||||
this(InetAddress.getLocalHost().getHostAddress(), startupSettings.getPort(), startupSettings.getLogLevel(), startupSettings.getGossipMembers(), startupSettings.getGossipSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the client's lists, gossiping parameters, and parse the startup config file.
|
||||
* @throws SocketException
|
||||
* @throws InterruptedException
|
||||
* @throws UnknownHostException
|
||||
*/
|
||||
public GossipService(String ipAddress, int port, int logLevel, ArrayList<GossipMember> gossipMembers, GossipSettings settings) throws InterruptedException, UnknownHostException {
|
||||
// Set the logging level.
|
||||
_logLevel = logLevel;
|
||||
|
||||
_gossipManager = new RandomGossipManager(ipAddress, port, settings, gossipMembers);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
_gossipManager.start();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
_gossipManager.shutdown();
|
||||
}
|
||||
|
||||
public static void error(Object message) {
|
||||
//if (_logLevel >= LogLevel.ERROR) printMessage(message, System.err);
|
||||
printMessage(message, System.err);
|
||||
}
|
||||
|
||||
public static void info(Object message) {
|
||||
//if (_logLevel >= LogLevel.INFO) printMessage(message, System.out);
|
||||
printMessage(message, System.out);
|
||||
}
|
||||
|
||||
public static void debug(Object message) {
|
||||
//if (_logLevel >= LogLevel.DEBUG) printMessage(message, System.out);
|
||||
printMessage(message, System.out);
|
||||
}
|
||||
|
||||
private static void printMessage(Object message, PrintStream out) {
|
||||
/**String addressString = "unknown";
|
||||
if (_me != null)
|
||||
addressString = _me.getAddress();
|
||||
out.println("[" + addressString + "][" + new Date().toString() + "] " + message);*/
|
||||
out.println("[" + new Date().toString() + "] " + message);
|
||||
}
|
||||
}
|
64
src/main/java/com/google/code/gossip/GossipSettings.java
Normal file
64
src/main/java/com/google/code/gossip/GossipSettings.java
Normal file
@ -0,0 +1,64 @@
|
||||
package com.google.code.gossip;
|
||||
|
||||
/**
|
||||
* In this object the settings used by the GossipService are held.
|
||||
*
|
||||
* @author harmenw
|
||||
*/
|
||||
public class GossipSettings {
|
||||
|
||||
/** Time between gossip'ing in ms. Default is 1 second. */
|
||||
private int _gossipInterval = 1000;
|
||||
|
||||
/** Time between cleanups in ms. Default is 10 seconds. */
|
||||
private int _cleanupInterval = 10000;
|
||||
|
||||
/**
|
||||
* Construct GossipSettings with default settings.
|
||||
*/
|
||||
public GossipSettings() {}
|
||||
|
||||
/**
|
||||
* Construct GossipSettings with given settings.
|
||||
* @param gossipInterval The gossip interval in ms.
|
||||
* @param cleanupInterval The cleanup interval in ms.
|
||||
*/
|
||||
public GossipSettings(int gossipInterval, int cleanupInterval) {
|
||||
_gossipInterval = gossipInterval;
|
||||
_cleanupInterval = cleanupInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gossip interval.
|
||||
* This is the time between a gossip message is send.
|
||||
* @param gossipInterval The gossip interval in ms.
|
||||
*/
|
||||
public void setGossipTimeout(int gossipInterval) {
|
||||
_gossipInterval = gossipInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cleanup interval.
|
||||
* This is the time between the last heartbeat received from a member and when it will be marked as dead.
|
||||
* @param cleanupInterval The cleanup interval in ms.
|
||||
*/
|
||||
public void setCleanupInterval(int cleanupInterval) {
|
||||
_cleanupInterval = cleanupInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gossip interval.
|
||||
* @return The gossip interval in ms.
|
||||
*/
|
||||
public int getGossipInterval() {
|
||||
return _gossipInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clean interval.
|
||||
* @return The cleanup interval.
|
||||
*/
|
||||
public int getCleanupInterval() {
|
||||
return _cleanupInterval;
|
||||
}
|
||||
}
|
61
src/main/java/com/google/code/gossip/GossipTimeoutTimer.java
Normal file
61
src/main/java/com/google/code/gossip/GossipTimeoutTimer.java
Normal file
@ -0,0 +1,61 @@
|
||||
package com.google.code.gossip;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.management.NotificationListener;
|
||||
import javax.management.timer.Timer;
|
||||
|
||||
/**
|
||||
* This object represents a timer for a gossip member.
|
||||
* When the timer has elapsed without being reset in the meantime, it will inform the GossipService about this
|
||||
* who in turn will put the gossip member on the dead list, because it is apparantly not alive anymore.
|
||||
*
|
||||
* @author joshclemm, harmenw
|
||||
*/
|
||||
public class GossipTimeoutTimer extends Timer {
|
||||
|
||||
/** The amount of time this timer waits before generating a wake-up event. */
|
||||
private long _sleepTime;
|
||||
|
||||
/** The gossip member this timer is for. */
|
||||
private LocalGossipMember _source;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Creates a reset-able timer that wakes up after millisecondsSleepTime.
|
||||
* @param millisecondsSleepTime The time for this timer to wait before an event.
|
||||
* @param service
|
||||
* @param member
|
||||
*/
|
||||
public GossipTimeoutTimer(long millisecondsSleepTime, NotificationListener notificationListener, LocalGossipMember member) {
|
||||
super();
|
||||
_sleepTime = millisecondsSleepTime;
|
||||
_source = member;
|
||||
addNotificationListener(notificationListener, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see javax.management.timer.Timer#start()
|
||||
*/
|
||||
public void start() {
|
||||
this.reset();
|
||||
super.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets timer to start counting down from original time.
|
||||
*/
|
||||
public void reset() {
|
||||
removeAllNotifications();
|
||||
setWakeupTime(_sleepTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new wake-up time for this timer.
|
||||
* @param milliseconds
|
||||
*/
|
||||
private void setWakeupTime(long milliseconds) {
|
||||
addNotification("type", "message", _source, new Date(System.currentTimeMillis()+milliseconds));
|
||||
}
|
||||
}
|
||||
|
42
src/main/java/com/google/code/gossip/LocalGossipMember.java
Normal file
42
src/main/java/com/google/code/gossip/LocalGossipMember.java
Normal file
@ -0,0 +1,42 @@
|
||||
package com.google.code.gossip;
|
||||
|
||||
import javax.management.NotificationListener;
|
||||
|
||||
/**
|
||||
* This object represent a gossip member with the properties known locally.
|
||||
* These objects are stored in the local list of gossip member.s
|
||||
*
|
||||
* @author harmenw
|
||||
*/
|
||||
public class LocalGossipMember extends GossipMember {
|
||||
/** The timeout timer for this gossip member. */
|
||||
private transient GossipTimeoutTimer timeoutTimer;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param host The hostname or IP address.
|
||||
* @param port The port number.
|
||||
* @param heartbeat The current heartbeat.
|
||||
* @param gossipService The GossipService object.
|
||||
* @param cleanupTimeout The cleanup timeout for this gossip member.
|
||||
*/
|
||||
public LocalGossipMember(String hostname, int port, int heartbeat, NotificationListener notificationListener, int cleanupTimeout) {
|
||||
super(hostname, port, heartbeat);
|
||||
|
||||
this.timeoutTimer = new GossipTimeoutTimer(cleanupTimeout, notificationListener, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the timeout timer.
|
||||
*/
|
||||
public void startTimeoutTimer() {
|
||||
this.timeoutTimer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the timeout timer.
|
||||
*/
|
||||
public void resetTimeoutTimer() {
|
||||
this.timeoutTimer.reset();
|
||||
}
|
||||
}
|
25
src/main/java/com/google/code/gossip/LogLevel.java
Normal file
25
src/main/java/com/google/code/gossip/LogLevel.java
Normal file
@ -0,0 +1,25 @@
|
||||
package com.google.code.gossip;
|
||||
|
||||
public class LogLevel {
|
||||
|
||||
public static final String CONFIG_ERROR = "ERROR";
|
||||
|
||||
public static final String CONFIG_INFO = "INFO";
|
||||
|
||||
public static final String CONFIG_DEBUG = "DEBUG";
|
||||
|
||||
public static final int ERROR = 1;
|
||||
public static final int INFO = 2;
|
||||
public static final int DEBUG = 3;
|
||||
|
||||
public static int fromString(String logLevel) {
|
||||
if (logLevel.equals(CONFIG_ERROR))
|
||||
return ERROR;
|
||||
else if (logLevel.equals(CONFIG_INFO))
|
||||
return INFO;
|
||||
else if (logLevel.equals(CONFIG_DEBUG))
|
||||
return DEBUG;
|
||||
else
|
||||
return INFO;
|
||||
}
|
||||
}
|
28
src/main/java/com/google/code/gossip/RemoteGossipMember.java
Normal file
28
src/main/java/com/google/code/gossip/RemoteGossipMember.java
Normal file
@ -0,0 +1,28 @@
|
||||
package com.google.code.gossip;
|
||||
|
||||
/**
|
||||
* The object represents a gossip member with the properties as received from a remote gossip member.
|
||||
*
|
||||
* @author harmenw
|
||||
*/
|
||||
public class RemoteGossipMember extends GossipMember {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param host The hostname or IP address.
|
||||
* @param port The port number.
|
||||
* @param heartbeat The current heartbeat.
|
||||
*/
|
||||
public RemoteGossipMember(String hostname, int port, int heartbeat) {
|
||||
super(hostname, port, heartbeat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a RemoteGossipMember with a heartbeat of 0.
|
||||
* @param host The hostname or IP address.
|
||||
* @param port The port number.
|
||||
*/
|
||||
public RemoteGossipMember(String hostname, int port) {
|
||||
super(hostname, port, 0);
|
||||
}
|
||||
}
|
161
src/main/java/com/google/code/gossip/StartupSettings.java
Normal file
161
src/main/java/com/google/code/gossip/StartupSettings.java
Normal file
@ -0,0 +1,161 @@
|
||||
package com.google.code.gossip;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* This object represents the settings used when starting the gossip service.
|
||||
*
|
||||
* @author harmenw
|
||||
*/
|
||||
public class StartupSettings {
|
||||
|
||||
/** The port to start the gossip service on. */
|
||||
private int _port;
|
||||
|
||||
/** The logging level of the gossip service. */
|
||||
private int _logLevel;
|
||||
|
||||
/** The gossip settings used at startup. */
|
||||
private GossipSettings _gossipSettings;
|
||||
|
||||
/** The list with gossip members to start with. */
|
||||
private ArrayList<GossipMember> _gossipMembers;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param port The port to start the service on.
|
||||
*/
|
||||
public StartupSettings(int port, int logLevel) {
|
||||
this(port, logLevel, new GossipSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param port The port to start the service on.
|
||||
*/
|
||||
public StartupSettings(int port, int logLevel, GossipSettings gossipSettings) {
|
||||
_port = port;
|
||||
_logLevel = logLevel;
|
||||
_gossipSettings = gossipSettings;
|
||||
_gossipMembers = new ArrayList<GossipMember>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the port of the gossip service.
|
||||
* @param port The port for the gossip service.
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
_port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port for the gossip service.
|
||||
* @return The port of the gossip service.
|
||||
*/
|
||||
public int getPort() {
|
||||
return _port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the log level of the gossip service.
|
||||
* @param logLevel The log level({LogLevel}).
|
||||
*/
|
||||
public void setLogLevel(int logLevel) {
|
||||
_logLevel = logLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the log level of the gossip service.
|
||||
* @return The log level.
|
||||
*/
|
||||
public int getLogLevel() {
|
||||
return _logLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the GossipSettings.
|
||||
* @return The GossipSettings object.
|
||||
*/
|
||||
public GossipSettings getGossipSettings() {
|
||||
return _gossipSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a gossip member to the list of members to start with.
|
||||
* @param member The member to add.
|
||||
*/
|
||||
public void addGossipMember(GossipMember member) {
|
||||
_gossipMembers.add(member);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list with gossip members.
|
||||
* @return The gossip members.
|
||||
*/
|
||||
public ArrayList<GossipMember> getGossipMembers() {
|
||||
return _gossipMembers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the settings for the gossip service from a JSON file.
|
||||
* @param jsonFile The file object which refers to the JSON config file.
|
||||
* @return The StartupSettings object with the settings from the config file.
|
||||
* @throws JSONException Thrown when the file is not well-formed JSON.
|
||||
* @throws FileNotFoundException Thrown when the file cannot be found.
|
||||
* @throws IOException Thrown when reading the file gives problems.
|
||||
*/
|
||||
public static StartupSettings fromJSONFile(File jsonFile) throws JSONException, FileNotFoundException, IOException {
|
||||
// Read the file to a String.
|
||||
BufferedReader br = new BufferedReader(new FileReader(jsonFile));
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
String line;
|
||||
while((line = br.readLine()) != null) {
|
||||
buffer.append(line.trim());
|
||||
}
|
||||
|
||||
// Lets parse the String as JSON.
|
||||
JSONObject jsonObject = new JSONArray(buffer.toString()).getJSONObject(0);
|
||||
|
||||
// Now get the port number.
|
||||
int port = jsonObject.getInt("port");
|
||||
|
||||
// Get the log level from the config file.
|
||||
int logLevel = LogLevel.fromString(jsonObject.getString("log_level"));
|
||||
|
||||
// Get the gossip_interval from the config file.
|
||||
int gossipInterval = jsonObject.getInt("gossip_interval");
|
||||
|
||||
// Get the cleanup_interval from the config file.
|
||||
int cleanupInterval = jsonObject.getInt("cleanup_interval");
|
||||
|
||||
System.out.println("Config [port: " + port + ", log_level: " + logLevel + ", gossip_interval: " + gossipInterval + ", cleanup_interval: " + cleanupInterval + "]");
|
||||
|
||||
// Initiate the settings with the port number.
|
||||
StartupSettings settings = new StartupSettings(port, logLevel, new GossipSettings(gossipInterval, cleanupInterval));
|
||||
|
||||
// Now iterate over the members from the config file and add them to the settings.
|
||||
System.out.print("Config-members [");
|
||||
JSONArray membersJSON = jsonObject.getJSONArray("members");
|
||||
for (int i=0; i<membersJSON.length(); i++) {
|
||||
JSONObject memberJSON = membersJSON.getJSONObject(i);
|
||||
RemoteGossipMember member = new RemoteGossipMember(memberJSON.getString("host"), memberJSON.getInt("port"));
|
||||
settings.addGossipMember(member);
|
||||
System.out.print(member.getAddress());
|
||||
if (i < (membersJSON.length() - 1))
|
||||
System.out.print(", ");
|
||||
}
|
||||
System.out.println("]");
|
||||
|
||||
// Return the created settings object.
|
||||
return settings;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.google.code.gossip.examples;
|
||||
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.google.code.gossip.GossipMember;
|
||||
import com.google.code.gossip.GossipService;
|
||||
import com.google.code.gossip.GossipSettings;
|
||||
import com.google.code.gossip.LogLevel;
|
||||
import com.google.code.gossip.RemoteGossipMember;
|
||||
|
||||
/**
|
||||
* This class is an example of how one could use the gossip service.
|
||||
* Here we start multiple gossip clients on this host as specified in the config file.
|
||||
*
|
||||
* @author harmenw
|
||||
*/
|
||||
public class GossipExample extends Thread {
|
||||
/** The number of clients to start. */
|
||||
private static final int NUMBER_OF_CLIENTS = 4;
|
||||
|
||||
/**
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
new GossipExample();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* This will start the this thread.
|
||||
*/
|
||||
public GossipExample() {
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Thread#run()
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
GossipSettings settings = new GossipSettings();
|
||||
|
||||
ArrayList<GossipService> clients = new ArrayList<GossipService>();
|
||||
|
||||
// Get my ip address.
|
||||
String myIpAddress = InetAddress.getLocalHost().getHostAddress();
|
||||
|
||||
// Create the gossip members and put them in a list and give them a port number starting with 2000.
|
||||
ArrayList<GossipMember> startupMembers = new ArrayList<GossipMember>();
|
||||
for (int i=0; i<NUMBER_OF_CLIENTS; ++i) {
|
||||
startupMembers.add(new RemoteGossipMember(myIpAddress, 2000+i));
|
||||
}
|
||||
|
||||
// Lets start the gossip clients.
|
||||
// Start the clients, waiting cleaning-interval + 1 second between them which will show the dead list handling.
|
||||
for (GossipMember member : startupMembers) {
|
||||
GossipService gossipService = new GossipService(myIpAddress, member.getPort(), LogLevel.DEBUG, startupMembers, settings);
|
||||
clients.add(gossipService);
|
||||
gossipService.start();
|
||||
sleep(settings.getCleanupInterval() + 1000);
|
||||
}
|
||||
|
||||
// After starting all gossip clients, first wait 10 seconds and then shut them down.
|
||||
sleep(10000);
|
||||
System.err.println("Going to shutdown all services...");
|
||||
// Since they all run in the same virtual machine and share the same executor, if one is shutdown they will all stop.
|
||||
clients.get(0).shutdown();
|
||||
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package com.google.code.gossip.manager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.google.code.gossip.GossipService;
|
||||
import com.google.code.gossip.LocalGossipMember;
|
||||
|
||||
/**
|
||||
* [The active thread: periodically send gossip request.]
|
||||
* The class handles gossiping the membership list.
|
||||
* This information is important to maintaining a common
|
||||
* state among all the nodes, and is important for detecting
|
||||
* failures.
|
||||
*/
|
||||
abstract public class ActiveGossipThread implements Runnable {
|
||||
|
||||
private GossipManager _gossipManager;
|
||||
|
||||
private AtomicBoolean _keepRunning;
|
||||
|
||||
public ActiveGossipThread(GossipManager gossipManager) {
|
||||
_gossipManager = gossipManager;
|
||||
|
||||
_keepRunning = new AtomicBoolean(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while(_keepRunning.get()) {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(_gossipManager.getSettings().getGossipInterval());
|
||||
sendMembershipList(_gossipManager.getMyself(), _gossipManager.getMemberList());
|
||||
} catch (InterruptedException e) {
|
||||
// This membership thread was interrupted externally, shutdown
|
||||
GossipService.debug("The ActiveGossipThread was interrupted externally, shutdown.");
|
||||
e.printStackTrace();
|
||||
_keepRunning.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
_keepRunning = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the sending of the membership list, after we have
|
||||
* incremented our own heartbeat.
|
||||
*/
|
||||
abstract protected void sendMembershipList(LocalGossipMember me, ArrayList<LocalGossipMember> memberList);
|
||||
|
||||
/**
|
||||
* Abstract method which should be implemented by a subclass.
|
||||
* This method should return a member of the list to gossip with.
|
||||
* @param memberList The list of members which are stored in the local list of members.
|
||||
* @return The chosen LocalGossipMember to gossip with.
|
||||
*/
|
||||
abstract protected LocalGossipMember selectPartner(ArrayList<LocalGossipMember> memberList);
|
||||
}
|
189
src/main/java/com/google/code/gossip/manager/GossipManager.java
Normal file
189
src/main/java/com/google/code/gossip/manager/GossipManager.java
Normal file
@ -0,0 +1,189 @@
|
||||
package com.google.code.gossip.manager;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.management.Notification;
|
||||
import javax.management.NotificationListener;
|
||||
|
||||
import com.google.code.gossip.GossipMember;
|
||||
import com.google.code.gossip.GossipService;
|
||||
import com.google.code.gossip.GossipSettings;
|
||||
import com.google.code.gossip.LocalGossipMember;
|
||||
|
||||
public abstract class GossipManager extends Thread implements NotificationListener {
|
||||
/** The maximal number of bytes the packet with the GOSSIP may be. (Default is 100 kb) */
|
||||
public static final int MAX_PACKET_SIZE = 102400;
|
||||
|
||||
/** The list of members which are in the gossip group (not including myself). */
|
||||
private ArrayList<LocalGossipMember> _memberList;
|
||||
|
||||
/** The list of members which are known to be dead. */
|
||||
private ArrayList<LocalGossipMember> _deadList;
|
||||
|
||||
/** The member I am representing. */
|
||||
private LocalGossipMember _me;
|
||||
|
||||
/** The settings for gossiping. */
|
||||
private GossipSettings _settings;
|
||||
|
||||
/** A boolean whether the gossip service should keep running. */
|
||||
private AtomicBoolean _gossipServiceRunning;
|
||||
|
||||
/** A ExecutorService used for executing the active and passive gossip threads. */
|
||||
private ExecutorService _gossipThreadExecutor;
|
||||
|
||||
private Class<? extends PassiveGossipThread> _passiveGossipThreadClass;
|
||||
|
||||
private Class<? extends ActiveGossipThread> _activeGossipThreadClass;
|
||||
|
||||
public GossipManager(Class<? extends PassiveGossipThread> passiveGossipThreadClass, Class<? extends ActiveGossipThread> activeGossipThreadClass, String address, int port, GossipSettings settings, ArrayList<GossipMember> gossipMembers) {
|
||||
// Set the active and passive gossip thread classes to use.
|
||||
_passiveGossipThreadClass = passiveGossipThreadClass;
|
||||
_activeGossipThreadClass = activeGossipThreadClass;
|
||||
|
||||
// Assign the GossipSettings to the instance variable.
|
||||
_settings = settings;
|
||||
|
||||
// Create the local gossip member which I am representing.
|
||||
_me = new LocalGossipMember(address, port, 0, this, settings.getCleanupInterval());
|
||||
|
||||
// Initialize the gossip members list.
|
||||
_memberList = new ArrayList<LocalGossipMember>();
|
||||
|
||||
// Initialize the dead gossip members list.
|
||||
_deadList = new ArrayList<LocalGossipMember>();
|
||||
|
||||
// Print the startup member list when the service is in debug mode.
|
||||
GossipService.debug("Startup member list:");
|
||||
GossipService.debug("---------------------");
|
||||
// First print out myself.
|
||||
GossipService.debug(_me);
|
||||
// Copy the list with members given to the local member list and print the member when in debug mode.
|
||||
for (GossipMember startupMember : gossipMembers) {
|
||||
if (!startupMember.equals(_me)) {
|
||||
LocalGossipMember member = new LocalGossipMember(startupMember.getHost(), startupMember.getPort(), 0, this, settings.getCleanupInterval());
|
||||
_memberList.add(member);
|
||||
GossipService.debug(member);
|
||||
} else {
|
||||
GossipService.info("Found myself in the members section of the configuration, you should not add the host itself to the members section.");
|
||||
}
|
||||
}
|
||||
|
||||
// Set the boolean for running the gossip service to true.
|
||||
_gossipServiceRunning = new AtomicBoolean(true);
|
||||
|
||||
// Add a shutdown hook so we can see when the service has been shutdown.
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
public void run() {
|
||||
GossipService.info("Service has been shutdown...");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* All timers associated with a member will trigger this method when it goes
|
||||
* off. The timer will go off if we have not heard from this member in
|
||||
* <code> _settings.T_CLEANUP </code> time.
|
||||
*/
|
||||
@Override
|
||||
public void handleNotification(Notification notification, Object handback) {
|
||||
|
||||
// Get the local gossip member associated with the notification.
|
||||
LocalGossipMember deadMember = (LocalGossipMember) notification.getUserData();
|
||||
|
||||
GossipService.info("Dead member detected: " + deadMember);
|
||||
|
||||
// Remove the member from the active member list.
|
||||
synchronized (this._memberList) {
|
||||
this._memberList.remove(deadMember);
|
||||
}
|
||||
|
||||
// Add the member to the dead member list.
|
||||
synchronized (this._deadList) {
|
||||
this._deadList.add(deadMember);
|
||||
}
|
||||
}
|
||||
|
||||
public GossipSettings getSettings() {
|
||||
return _settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a clone of the memberlist.
|
||||
* @return
|
||||
*/
|
||||
public ArrayList<LocalGossipMember> getMemberList() {
|
||||
return _memberList;
|
||||
}
|
||||
|
||||
public LocalGossipMember getMyself() {
|
||||
return _me;
|
||||
}
|
||||
|
||||
public ArrayList<LocalGossipMember> getDeadList() {
|
||||
return _deadList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the client. Specifically, start the various cycles for this protocol.
|
||||
* Start the gossip thread and start the receiver thread.
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void run() {
|
||||
// Start all timers except for me
|
||||
for (LocalGossipMember member : _memberList) {
|
||||
if (member != _me) {
|
||||
member.startTimeoutTimer();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_gossipThreadExecutor = Executors.newCachedThreadPool();
|
||||
// The receiver thread is a passive player that handles
|
||||
// merging incoming membership lists from other neighbors.
|
||||
_gossipThreadExecutor.execute(_passiveGossipThreadClass.getConstructor(GossipManager.class).newInstance(this));
|
||||
// The gossiper thread is an active player that
|
||||
// selects a neighbor to share its membership list
|
||||
_gossipThreadExecutor.execute(_activeGossipThreadClass.getConstructor(GossipManager.class).newInstance(this));
|
||||
} catch (IllegalArgumentException e1) {
|
||||
e1.printStackTrace();
|
||||
} catch (SecurityException e1) {
|
||||
e1.printStackTrace();
|
||||
} catch (InstantiationException e1) {
|
||||
e1.printStackTrace();
|
||||
} catch (IllegalAccessException e1) {
|
||||
e1.printStackTrace();
|
||||
} catch (InvocationTargetException e1) {
|
||||
e1.printStackTrace();
|
||||
} catch (NoSuchMethodException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
// Potentially, you could kick off more threads here
|
||||
// that could perform additional data synching
|
||||
|
||||
GossipService.info("The GossipService is started.");
|
||||
|
||||
// keep the main thread around
|
||||
while(_gossipServiceRunning.get()) {
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
GossipService.info("The GossipClient was interrupted.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the gossip service.
|
||||
*/
|
||||
public void shutdown() {
|
||||
_gossipThreadExecutor.shutdown();
|
||||
_gossipServiceRunning.set(false);
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package com.google.code.gossip.manager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.google.code.gossip.GossipMember;
|
||||
import com.google.code.gossip.GossipService;
|
||||
import com.google.code.gossip.RemoteGossipMember;
|
||||
|
||||
/**
|
||||
* [The passive thread: reply to incoming gossip request.]
|
||||
* This class handles the passive cycle, where this client
|
||||
* has received an incoming message. For now, this message
|
||||
* is always the membership list, but if you choose to gossip
|
||||
* additional information, you will need some logic to determine
|
||||
* the incoming message.
|
||||
*/
|
||||
abstract public class PassiveGossipThread implements Runnable {
|
||||
|
||||
/** The socket used for the passive thread of the gossip service. */
|
||||
private DatagramSocket _server;
|
||||
|
||||
private GossipManager _gossipManager;
|
||||
|
||||
private AtomicBoolean _keepRunning;
|
||||
|
||||
public PassiveGossipThread(GossipManager gossipManager) {
|
||||
_gossipManager = gossipManager;
|
||||
|
||||
// Start the service on the given port number.
|
||||
try {
|
||||
_server = new DatagramSocket(_gossipManager.getMyself().getPort());
|
||||
|
||||
// The server successfully started on the current port.
|
||||
GossipService.info("Gossip service successfully initialized on port " + _gossipManager.getMyself().getPort());
|
||||
GossipService.debug("I am " + _gossipManager.getMyself());
|
||||
} catch (SocketException ex) {
|
||||
// The port is probably already in use.
|
||||
_server = null;
|
||||
// Let's communicate this to the user.
|
||||
GossipService.error("Error while starting the gossip service on port " + _gossipManager.getMyself().getPort() + ": " + ex.getMessage());
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
_keepRunning = new AtomicBoolean(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while(_keepRunning.get()) {
|
||||
try {
|
||||
// Create a byte array with the size of the buffer.
|
||||
byte[] buf = new byte[_server.getReceiveBufferSize()];
|
||||
DatagramPacket p = new DatagramPacket(buf, buf.length);
|
||||
_server.receive(p);
|
||||
GossipService.debug("A message has been received from " + p.getAddress() + ":" + p.getPort() + ".");
|
||||
|
||||
int packet_length = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int shift = (4 - 1 - i) * 8;
|
||||
packet_length += (buf[i] & 0x000000FF) << shift;
|
||||
}
|
||||
|
||||
// Check whether the package is smaller than the maximal packet length.
|
||||
// A package larger than this would not be possible to be send from a GossipService,
|
||||
// since this is check before sending the message.
|
||||
// This could normally only occur when the list of members is very big,
|
||||
// or when the packet is misformed, and the first 4 bytes is not the right in anymore.
|
||||
// For this reason we regards the message.
|
||||
if (packet_length <= GossipManager.MAX_PACKET_SIZE) {
|
||||
|
||||
byte[] json_bytes = new byte[packet_length];
|
||||
for (int i=0; i<packet_length; i++) {
|
||||
json_bytes[i] = buf[i+4];
|
||||
}
|
||||
|
||||
// Extract the members out of the packet
|
||||
String receivedMessage = new String(json_bytes);
|
||||
GossipService.debug("Received message (" + packet_length + " bytes): " + receivedMessage);
|
||||
|
||||
try {
|
||||
|
||||
ArrayList<GossipMember> remoteGossipMembers = new ArrayList<GossipMember>();
|
||||
|
||||
RemoteGossipMember senderMember = null;
|
||||
|
||||
GossipService.debug("Received member list:");
|
||||
// Convert the received JSON message to a JSON array.
|
||||
JSONArray jsonArray = new JSONArray(receivedMessage);
|
||||
// The JSON array should contain all members.
|
||||
// Let's iterate over them.
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
JSONObject memberJSONObject = jsonArray.getJSONObject(i);
|
||||
// Now the array should contain 3 objects (hostname, port and heartbeat).
|
||||
if (memberJSONObject.length() == 3) {
|
||||
// Ok, now let's create the member object.
|
||||
RemoteGossipMember member = new RemoteGossipMember(memberJSONObject.getString(GossipMember.JSON_HOST), memberJSONObject.getInt(GossipMember.JSON_PORT), memberJSONObject.getInt(GossipMember.JSON_HEARTBEAT));
|
||||
GossipService.debug(member.toString());
|
||||
|
||||
// This is the first member found, so this should be the member who is communicating with me.
|
||||
if (i == 0) {
|
||||
senderMember = member;
|
||||
}
|
||||
|
||||
remoteGossipMembers.add(member);
|
||||
} else {
|
||||
GossipService.error("The received member object does not contain 3 objects:\n" + memberJSONObject.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Merge our list with the one we just received
|
||||
mergeLists(_gossipManager, senderMember, remoteGossipMembers);
|
||||
|
||||
} catch (JSONException e) {
|
||||
GossipService.error("The received message is not well-formed JSON. The following message has been dropped:\n" + receivedMessage);
|
||||
}
|
||||
|
||||
} else {
|
||||
GossipService.error("The received message is not of the expected size, it has been dropped.");
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
_keepRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method for merging the local and remote list.
|
||||
* @param gossipManager The GossipManager for retrieving the local members and dead members list.
|
||||
* @param senderMember The member who is sending this list, this could be used to send a response if the remote list contains out-dated information.
|
||||
* @param remoteList The list of members known at the remote side.
|
||||
*/
|
||||
abstract protected void mergeLists(GossipManager gossipManager, RemoteGossipMember senderMember, ArrayList<GossipMember> remoteList);
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package com.google.code.gossip.manager.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.google.code.gossip.GossipMember;
|
||||
import com.google.code.gossip.GossipService;
|
||||
import com.google.code.gossip.LocalGossipMember;
|
||||
import com.google.code.gossip.RemoteGossipMember;
|
||||
import com.google.code.gossip.manager.GossipManager;
|
||||
import com.google.code.gossip.manager.PassiveGossipThread;
|
||||
|
||||
public class OnlyProcessReceivedPassiveGossipThread extends PassiveGossipThread {
|
||||
|
||||
public OnlyProcessReceivedPassiveGossipThread(GossipManager gossipManager) {
|
||||
super(gossipManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge remote list (received from peer), and our local member list.
|
||||
* Simply, we must update the heartbeats that the remote list has with
|
||||
* our list. Also, some additional logic is needed to make sure we have
|
||||
* not timed out a member and then immediately received a list with that
|
||||
* member.
|
||||
* @param remoteList
|
||||
*/
|
||||
protected void mergeLists(GossipManager gossipManager, RemoteGossipMember senderMember, ArrayList<GossipMember> remoteList) {
|
||||
|
||||
synchronized (gossipManager.getDeadList()) {
|
||||
|
||||
synchronized (gossipManager.getMemberList()) {
|
||||
|
||||
for (GossipMember remoteMember : remoteList) {
|
||||
// Skip myself. We don't want ourselves in the local member list.
|
||||
if (!remoteMember.equals(gossipManager.getMyself())) {
|
||||
if (gossipManager.getMemberList().contains(remoteMember)) {
|
||||
GossipService.debug("The local list already contains the remote member (" + remoteMember + ").");
|
||||
// The local memberlist contains the remote member.
|
||||
LocalGossipMember localMember = gossipManager.getMemberList().get(gossipManager.getMemberList().indexOf(remoteMember));
|
||||
|
||||
// Let's synchronize it's heartbeat.
|
||||
if (remoteMember.getHeartbeat() > localMember.getHeartbeat()) {
|
||||
// update local list with latest heartbeat
|
||||
localMember.setHeartbeat(remoteMember.getHeartbeat());
|
||||
// and reset the timeout of that member
|
||||
localMember.resetTimeoutTimer();
|
||||
}
|
||||
// TODO: Otherwise, should we inform the other when the heartbeat is already higher?
|
||||
} else {
|
||||
// The local list does not contain the remote member.
|
||||
GossipService.debug("The local list does not contain the remote member (" + remoteMember + ").");
|
||||
|
||||
// The remote member is either brand new, or a previously declared dead member.
|
||||
// If its dead, check the heartbeat because it may have come back from the dead.
|
||||
if (gossipManager.getDeadList().contains(remoteMember)) {
|
||||
// The remote member is known here as a dead member.
|
||||
GossipService.debug("The remote member is known here as a dead member.");
|
||||
LocalGossipMember localDeadMember = gossipManager.getDeadList().get(gossipManager.getDeadList().indexOf(remoteMember));
|
||||
// If a member is restarted the heartbeat will restart from 1, so we should check that here.
|
||||
// So a member can become from the dead when it is either larger than a previous heartbeat (due to network failure)
|
||||
// or when the heartbeat is 1 (after a restart of the service).
|
||||
// TODO: What if the first message of a gossip service is sent to a dead node? The second member will receive a heartbeat of two.
|
||||
// TODO: The above does happen. Maybe a special message for a revived member?
|
||||
// TODO: Or maybe when a member is declared dead for more than _settings.getCleanupInterval() ms, reset the heartbeat to 0.
|
||||
// It will then accept a revived member.
|
||||
// The above is now handle by checking whether the heartbeat differs _settings.getCleanupInterval(), it must be restarted.
|
||||
if (remoteMember.getHeartbeat() == 1
|
||||
|| ((localDeadMember.getHeartbeat() - remoteMember.getHeartbeat()) * -1) > (gossipManager.getSettings().getCleanupInterval() / 1000)
|
||||
|| remoteMember.getHeartbeat() > localDeadMember.getHeartbeat()) {
|
||||
GossipService.debug("The remote member is back from the dead. We will remove it from the dead list and add it as a new member.");
|
||||
// The remote member is back from the dead.
|
||||
// Remove it from the dead list.
|
||||
gossipManager.getDeadList().remove(localDeadMember);
|
||||
// Add it as a new member and add it to the member list.
|
||||
LocalGossipMember newLocalMember = new LocalGossipMember(remoteMember.getHost(), remoteMember.getPort(), remoteMember.getHeartbeat(), gossipManager, gossipManager.getSettings().getCleanupInterval());
|
||||
gossipManager.getMemberList().add(newLocalMember);
|
||||
newLocalMember.startTimeoutTimer();
|
||||
GossipService.info("Removed remote member " + remoteMember.getAddress() + " from dead list and added to local member list.");
|
||||
}
|
||||
} else {
|
||||
// Brand spanking new member - welcome.
|
||||
LocalGossipMember newLocalMember = new LocalGossipMember(remoteMember.getHost(), remoteMember.getPort(), remoteMember.getHeartbeat(), gossipManager, gossipManager.getSettings().getCleanupInterval());
|
||||
gossipManager.getMemberList().add(newLocalMember);
|
||||
newLocalMember.startTimeoutTimer();
|
||||
GossipService.info("Added new remote member " + remoteMember.getAddress() + " to local member list.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package com.google.code.gossip.manager.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import com.google.code.gossip.GossipService;
|
||||
import com.google.code.gossip.LocalGossipMember;
|
||||
import com.google.code.gossip.manager.ActiveGossipThread;
|
||||
import com.google.code.gossip.manager.GossipManager;
|
||||
import com.sun.xml.internal.ws.util.ByteArrayBuffer;
|
||||
|
||||
abstract public class SendMembersActiveGossipThread extends ActiveGossipThread {
|
||||
|
||||
public SendMembersActiveGossipThread(GossipManager gossipManager) {
|
||||
super(gossipManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the sending of the membership list, after we have
|
||||
* incremented our own heartbeat.
|
||||
*/
|
||||
protected void sendMembershipList(LocalGossipMember me, ArrayList<LocalGossipMember> memberList) {
|
||||
GossipService.debug("Send sendMembershipList() is called.");
|
||||
|
||||
// Increase the heartbeat of myself by 1.
|
||||
me.setHeartbeat(me.getHeartbeat() + 1);
|
||||
|
||||
synchronized (memberList) {
|
||||
try {
|
||||
LocalGossipMember member = selectPartner(memberList);
|
||||
|
||||
if (member != null) {
|
||||
InetAddress dest = InetAddress.getByName(member.getHost());
|
||||
|
||||
// Create a StringBuffer for the JSON message.
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
GossipService.debug("Sending memberlist to " + dest + ":" + member.getPort());
|
||||
GossipService.debug("---------------------");
|
||||
|
||||
// First write myself, append the JSON representation of the member to the buffer.
|
||||
jsonArray.put(me.toJSONObject());
|
||||
GossipService.debug(me);
|
||||
|
||||
// Then write the others.
|
||||
for (int i=0; i<memberList.size(); i++) {
|
||||
LocalGossipMember other = memberList.get(i);
|
||||
// Append the JSON representation of the member to the buffer.
|
||||
jsonArray.put(other.toJSONObject());
|
||||
GossipService.debug(other);
|
||||
}
|
||||
GossipService.debug("---------------------");
|
||||
|
||||
// Write the objects to a byte array.
|
||||
byte[] json_bytes = jsonArray.toString().getBytes();
|
||||
|
||||
int packet_length = json_bytes.length;
|
||||
|
||||
if (packet_length < GossipManager.MAX_PACKET_SIZE) {
|
||||
|
||||
// Convert the packet length to the byte representation of the int.
|
||||
byte[] length_bytes = new byte[4];
|
||||
length_bytes[0] =(byte)( packet_length >> 24 );
|
||||
length_bytes[1] =(byte)( (packet_length << 8) >> 24 );
|
||||
length_bytes[2] =(byte)( (packet_length << 16) >> 24 );
|
||||
length_bytes[3] =(byte)( (packet_length << 24) >> 24 );
|
||||
|
||||
|
||||
GossipService.debug("Sending message ("+packet_length+" bytes): " + jsonArray.toString());
|
||||
|
||||
|
||||
ByteArrayBuffer byteBuffer = new ByteArrayBuffer();
|
||||
// Write the first 4 bytes with the length of the rest of the packet.
|
||||
byteBuffer.write(length_bytes);
|
||||
// Write the json data.
|
||||
byteBuffer.write(json_bytes);
|
||||
|
||||
byte[] buf = byteBuffer.getRawData();
|
||||
|
||||
/*
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + json_bytes.length);
|
||||
byteBuffer.put(length_bytes);
|
||||
byteBuffer.put(json_bytes);
|
||||
byte[] buf = byteBuffer.array();
|
||||
*/
|
||||
|
||||
DatagramSocket socket = new DatagramSocket();
|
||||
DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length, dest, member.getPort());
|
||||
socket.send(datagramPacket);
|
||||
socket.close();
|
||||
} else {
|
||||
GossipService.error("The length of the to be send message is too large (" + packet_length + " > " + GossipManager.MAX_PACKET_SIZE + ").");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.google.code.gossip.manager.random;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
import com.google.code.gossip.GossipService;
|
||||
import com.google.code.gossip.LocalGossipMember;
|
||||
import com.google.code.gossip.manager.GossipManager;
|
||||
import com.google.code.gossip.manager.impl.SendMembersActiveGossipThread;
|
||||
|
||||
public class RandomActiveGossipThread extends SendMembersActiveGossipThread {
|
||||
|
||||
/** The Random used for choosing a member to gossip with. */
|
||||
private Random _random;
|
||||
|
||||
public RandomActiveGossipThread(GossipManager gossipManager) {
|
||||
super(gossipManager);
|
||||
|
||||
// Initialize the random used for deciding on which gossip member to gossip with.
|
||||
_random = new Random();
|
||||
}
|
||||
|
||||
/**
|
||||
* [The selectToSend() function.]
|
||||
* Find a random peer from the local membership list.
|
||||
* In the case where this client is the only member in the list, this method will return null.
|
||||
* @return Member random member if list is greater than 1, null otherwise
|
||||
*/
|
||||
protected LocalGossipMember selectPartner(ArrayList<LocalGossipMember> memberList) {
|
||||
LocalGossipMember member = null;
|
||||
|
||||
// We can only send a message if there are actually other members.
|
||||
if (memberList.size() > 0) {
|
||||
// Get the index of the random member.
|
||||
int randomNeighborIndex = _random.nextInt(memberList.size());
|
||||
member = memberList.get(randomNeighborIndex);
|
||||
} else {
|
||||
GossipService.debug("I am alone in this world.");
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.google.code.gossip.manager.random;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.google.code.gossip.GossipMember;
|
||||
import com.google.code.gossip.GossipSettings;
|
||||
import com.google.code.gossip.manager.GossipManager;
|
||||
import com.google.code.gossip.manager.impl.OnlyProcessReceivedPassiveGossipThread;
|
||||
|
||||
public class RandomGossipManager extends GossipManager {
|
||||
public RandomGossipManager(String address, int port, GossipSettings settings, ArrayList<GossipMember> gossipMembers) {
|
||||
super(OnlyProcessReceivedPassiveGossipThread.class, RandomActiveGossipThread.class, address, port, settings, gossipMembers);
|
||||
}
|
||||
}
|
279
src/main/java/org/json/CDL.java
Normal file
279
src/main/java/org/json/CDL.java
Normal file
@ -0,0 +1,279 @@
|
||||
package org.json;
|
||||
|
||||
/*
|
||||
Copyright (c) 2002 JSON.org
|
||||
|
||||
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 shall be used for Good, not Evil.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This provides static methods to convert comma delimited text into a
|
||||
* JSONArray, and to covert a JSONArray into comma delimited text. Comma
|
||||
* delimited text is a very popular format for data interchange. It is
|
||||
* understood by most database, spreadsheet, and organizer programs.
|
||||
* <p>
|
||||
* Each row of text represents a row in a table or a data record. Each row
|
||||
* ends with a NEWLINE character. Each row contains one or more values.
|
||||
* Values are separated by commas. A value can contain any character except
|
||||
* for comma, unless is is wrapped in single quotes or double quotes.
|
||||
* <p>
|
||||
* The first row usually contains the names of the columns.
|
||||
* <p>
|
||||
* A comma delimited list can be converted into a JSONArray of JSONObjects.
|
||||
* The names for the elements in the JSONObjects can be taken from the names
|
||||
* in the first row.
|
||||
* @author JSON.org
|
||||
* @version 2009-09-11
|
||||
*/
|
||||
public class CDL {
|
||||
|
||||
/**
|
||||
* Get the next value. The value can be wrapped in quotes. The value can
|
||||
* be empty.
|
||||
* @param x A JSONTokener of the source text.
|
||||
* @return The value string, or null if empty.
|
||||
* @throws JSONException if the quoted string is badly formed.
|
||||
*/
|
||||
private static String getValue(JSONTokener x) throws JSONException {
|
||||
char c;
|
||||
char q;
|
||||
StringBuffer sb;
|
||||
do {
|
||||
c = x.next();
|
||||
} while (c == ' ' || c == '\t');
|
||||
switch (c) {
|
||||
case 0:
|
||||
return null;
|
||||
case '"':
|
||||
case '\'':
|
||||
q = c;
|
||||
sb = new StringBuffer();
|
||||
for (;;) {
|
||||
c = x.next();
|
||||
if (c == q) {
|
||||
break;
|
||||
}
|
||||
if (c == 0 || c == '\n' || c == '\r') {
|
||||
throw x.syntaxError("Missing close quote '" + q + "'.");
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
case ',':
|
||||
x.back();
|
||||
return "";
|
||||
default:
|
||||
x.back();
|
||||
return x.nextTo(',');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of strings from a row of comma delimited values.
|
||||
* @param x A JSONTokener of the source text.
|
||||
* @return A JSONArray of strings.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException {
|
||||
JSONArray ja = new JSONArray();
|
||||
for (;;) {
|
||||
String value = getValue(x);
|
||||
char c = x.next();
|
||||
if (value == null ||
|
||||
(ja.length() == 0 && value.length() == 0 && c != ',')) {
|
||||
return null;
|
||||
}
|
||||
ja.put(value);
|
||||
for (;;) {
|
||||
if (c == ',') {
|
||||
break;
|
||||
}
|
||||
if (c != ' ') {
|
||||
if (c == '\n' || c == '\r' || c == 0) {
|
||||
return ja;
|
||||
}
|
||||
throw x.syntaxError("Bad character '" + c + "' (" +
|
||||
(int)c + ").");
|
||||
}
|
||||
c = x.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONObject from a row of comma delimited text, using a
|
||||
* parallel JSONArray of strings to provides the names of the elements.
|
||||
* @param names A JSONArray of names. This is commonly obtained from the
|
||||
* first row of a comma delimited text file using the rowToJSONArray
|
||||
* method.
|
||||
* @param x A JSONTokener of the source text.
|
||||
* @return A JSONObject combining the names and values.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x)
|
||||
throws JSONException {
|
||||
JSONArray ja = rowToJSONArray(x);
|
||||
return ja != null ? ja.toJSONObject(names) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of JSONObjects from a comma delimited text string,
|
||||
* using the first row as a source of names.
|
||||
* @param string The comma delimited text.
|
||||
* @return A JSONArray of JSONObjects.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONArray toJSONArray(String string) throws JSONException {
|
||||
return toJSONArray(new JSONTokener(string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of JSONObjects from a comma delimited text string,
|
||||
* using the first row as a source of names.
|
||||
* @param x The JSONTokener containing the comma delimited text.
|
||||
* @return A JSONArray of JSONObjects.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONArray toJSONArray(JSONTokener x) throws JSONException {
|
||||
return toJSONArray(rowToJSONArray(x), x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of JSONObjects from a comma delimited text string
|
||||
* using a supplied JSONArray as the source of element names.
|
||||
* @param names A JSONArray of strings.
|
||||
* @param string The comma delimited text.
|
||||
* @return A JSONArray of JSONObjects.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONArray toJSONArray(JSONArray names, String string)
|
||||
throws JSONException {
|
||||
return toJSONArray(names, new JSONTokener(string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSONArray of JSONObjects from a comma delimited text string
|
||||
* using a supplied JSONArray as the source of element names.
|
||||
* @param names A JSONArray of strings.
|
||||
* @param x A JSONTokener of the source text.
|
||||
* @return A JSONArray of JSONObjects.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONArray toJSONArray(JSONArray names, JSONTokener x)
|
||||
throws JSONException {
|
||||
if (names == null || names.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
JSONArray ja = new JSONArray();
|
||||
for (;;) {
|
||||
JSONObject jo = rowToJSONObject(names, x);
|
||||
if (jo == null) {
|
||||
break;
|
||||
}
|
||||
ja.put(jo);
|
||||
}
|
||||
if (ja.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
return ja;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Produce a comma delimited text row from a JSONArray. Values containing
|
||||
* the comma character will be quoted. Troublesome characters may be
|
||||
* removed.
|
||||
* @param ja A JSONArray of strings.
|
||||
* @return A string ending in NEWLINE.
|
||||
*/
|
||||
public static String rowToString(JSONArray ja) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < ja.length(); i += 1) {
|
||||
if (i > 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
Object o = ja.opt(i);
|
||||
if (o != null) {
|
||||
String s = o.toString();
|
||||
if (s.length() > 0 && (s.indexOf(',') >= 0 || s.indexOf('\n') >= 0 ||
|
||||
s.indexOf('\r') >= 0 || s.indexOf(0) >= 0 ||
|
||||
s.charAt(0) == '"')) {
|
||||
sb.append('"');
|
||||
int length = s.length();
|
||||
for (int j = 0; j < length; j += 1) {
|
||||
char c = s.charAt(j);
|
||||
if (c >= ' ' && c != '"') {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
sb.append('"');
|
||||
} else {
|
||||
sb.append(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a comma delimited text from a JSONArray of JSONObjects. The
|
||||
* first row will be a list of names obtained by inspecting the first
|
||||
* JSONObject.
|
||||
* @param ja A JSONArray of JSONObjects.
|
||||
* @return A comma delimited text.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static String toString(JSONArray ja) throws JSONException {
|
||||
JSONObject jo = ja.optJSONObject(0);
|
||||
if (jo != null) {
|
||||
JSONArray names = jo.names();
|
||||
if (names != null) {
|
||||
return rowToString(names) + toString(names, ja);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a comma delimited text from a JSONArray of JSONObjects using
|
||||
* a provided list of names. The list of names is not included in the
|
||||
* output.
|
||||
* @param names A JSONArray of strings.
|
||||
* @param ja A JSONArray of JSONObjects.
|
||||
* @return A comma delimited text.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static String toString(JSONArray names, JSONArray ja)
|
||||
throws JSONException {
|
||||
if (names == null || names.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < ja.length(); i += 1) {
|
||||
JSONObject jo = ja.optJSONObject(i);
|
||||
if (jo != null) {
|
||||
sb.append(rowToString(jo.toJSONArray(names)));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
918
src/main/java/org/json/JSONArray.java
Normal file
918
src/main/java/org/json/JSONArray.java
Normal file
@ -0,0 +1,918 @@
|
||||
package org.json;
|
||||
|
||||
/*
|
||||
Copyright (c) 2002 JSON.org
|
||||
|
||||
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 shall be used for Good, not Evil.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A JSONArray is an ordered sequence of values. Its external text form is a
|
||||
* string wrapped in square brackets with commas separating the values. The
|
||||
* internal form is an object having <code>get</code> and <code>opt</code>
|
||||
* methods for accessing the values by index, and <code>put</code> methods for
|
||||
* adding or replacing values. The values can be any of these types:
|
||||
* <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>,
|
||||
* <code>Number</code>, <code>String</code>, or the
|
||||
* <code>JSONObject.NULL object</code>.
|
||||
* <p>
|
||||
* The constructor can convert a JSON text into a Java object. The
|
||||
* <code>toString</code> method converts to JSON text.
|
||||
* <p>
|
||||
* A <code>get</code> method returns a value if one can be found, and throws an
|
||||
* exception if one cannot be found. An <code>opt</code> method returns a
|
||||
* default value instead of throwing an exception, and so is useful for
|
||||
* obtaining optional values.
|
||||
* <p>
|
||||
* The generic <code>get()</code> and <code>opt()</code> methods return an
|
||||
* object which you can cast or query for type. There are also typed
|
||||
* <code>get</code> and <code>opt</code> methods that do type checking and type
|
||||
* coercion for you.
|
||||
* <p>
|
||||
* The texts produced by the <code>toString</code> methods strictly conform to
|
||||
* JSON syntax rules. The constructors are more forgiving in the texts they will
|
||||
* accept:
|
||||
* <ul>
|
||||
* <li>An extra <code>,</code> <small>(comma)</small> may appear just
|
||||
* before the closing bracket.</li>
|
||||
* <li>The <code>null</code> value will be inserted when there
|
||||
* is <code>,</code> <small>(comma)</small> elision.</li>
|
||||
* <li>Strings may be quoted with <code>'</code> <small>(single
|
||||
* quote)</small>.</li>
|
||||
* <li>Strings do not need to be quoted at all if they do not begin with a quote
|
||||
* or single quote, and if they do not contain leading or trailing spaces,
|
||||
* and if they do not contain any of these characters:
|
||||
* <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers
|
||||
* and if they are not the reserved words <code>true</code>,
|
||||
* <code>false</code>, or <code>null</code>.</li>
|
||||
* <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as
|
||||
* well as by <code>,</code> <small>(comma)</small>.</li>
|
||||
* <li>Numbers may have the
|
||||
* <code>0x-</code> <small>(hex)</small> prefix.</li>
|
||||
* </ul>
|
||||
|
||||
* @author JSON.org
|
||||
* @version 2009-04-14
|
||||
*/
|
||||
public class JSONArray {
|
||||
|
||||
|
||||
/**
|
||||
* The arrayList where the JSONArray's properties are kept.
|
||||
*/
|
||||
private ArrayList myArrayList;
|
||||
|
||||
|
||||
/**
|
||||
* Construct an empty JSONArray.
|
||||
*/
|
||||
public JSONArray() {
|
||||
this.myArrayList = new ArrayList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a JSONArray from a JSONTokener.
|
||||
* @param x A JSONTokener
|
||||
* @throws JSONException If there is a syntax error.
|
||||
*/
|
||||
public JSONArray(JSONTokener x) throws JSONException {
|
||||
this();
|
||||
char c = x.nextClean();
|
||||
char q;
|
||||
if (c == '[') {
|
||||
q = ']';
|
||||
} else if (c == '(') {
|
||||
q = ')';
|
||||
} else {
|
||||
throw x.syntaxError("A JSONArray text must start with '['");
|
||||
}
|
||||
if (x.nextClean() == ']') {
|
||||
return;
|
||||
}
|
||||
x.back();
|
||||
for (;;) {
|
||||
if (x.nextClean() == ',') {
|
||||
x.back();
|
||||
this.myArrayList.add(null);
|
||||
} else {
|
||||
x.back();
|
||||
this.myArrayList.add(x.nextValue());
|
||||
}
|
||||
c = x.nextClean();
|
||||
switch (c) {
|
||||
case ';':
|
||||
case ',':
|
||||
if (x.nextClean() == ']') {
|
||||
return;
|
||||
}
|
||||
x.back();
|
||||
break;
|
||||
case ']':
|
||||
case ')':
|
||||
if (q != c) {
|
||||
throw x.syntaxError("Expected a '" + new Character(q) + "'");
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw x.syntaxError("Expected a ',' or ']'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a JSONArray from a source JSON text.
|
||||
* @param source A string that begins with
|
||||
* <code>[</code> <small>(left bracket)</small>
|
||||
* and ends with <code>]</code> <small>(right bracket)</small>.
|
||||
* @throws JSONException If there is a syntax error.
|
||||
*/
|
||||
public JSONArray(String source) throws JSONException {
|
||||
this(new JSONTokener(source));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a JSONArray from a Collection.
|
||||
* @param collection A Collection.
|
||||
*/
|
||||
public JSONArray(Collection collection) {
|
||||
this.myArrayList = new ArrayList();
|
||||
if (collection != null) {
|
||||
Iterator iter = collection.iterator();
|
||||
while (iter.hasNext()) {
|
||||
Object o = iter.next();
|
||||
this.myArrayList.add(JSONObject.wrap(o));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a JSONArray from an array
|
||||
* @throws JSONException If not an array.
|
||||
*/
|
||||
public JSONArray(Object array) throws JSONException {
|
||||
this();
|
||||
if (array.getClass().isArray()) {
|
||||
int length = Array.getLength(array);
|
||||
for (int i = 0; i < length; i += 1) {
|
||||
this.put(JSONObject.wrap(Array.get(array, i)));
|
||||
}
|
||||
} else {
|
||||
throw new JSONException(
|
||||
"JSONArray initial value should be a string or collection or array.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the object value associated with an index.
|
||||
* @param index
|
||||
* The index must be between 0 and length() - 1.
|
||||
* @return An object value.
|
||||
* @throws JSONException If there is no value for the index.
|
||||
*/
|
||||
public Object get(int index) throws JSONException {
|
||||
Object o = opt(index);
|
||||
if (o == null) {
|
||||
throw new JSONException("JSONArray[" + index + "] not found.");
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the boolean value associated with an index.
|
||||
* The string values "true" and "false" are converted to boolean.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return The truth.
|
||||
* @throws JSONException If there is no value for the index or if the
|
||||
* value is not convertable to boolean.
|
||||
*/
|
||||
public boolean getBoolean(int index) throws JSONException {
|
||||
Object o = get(index);
|
||||
if (o.equals(Boolean.FALSE) ||
|
||||
(o instanceof String &&
|
||||
((String)o).equalsIgnoreCase("false"))) {
|
||||
return false;
|
||||
} else if (o.equals(Boolean.TRUE) ||
|
||||
(o instanceof String &&
|
||||
((String)o).equalsIgnoreCase("true"))) {
|
||||
return true;
|
||||
}
|
||||
throw new JSONException("JSONArray[" + index + "] is not a Boolean.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the double value associated with an index.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return The value.
|
||||
* @throws JSONException If the key is not found or if the value cannot
|
||||
* be converted to a number.
|
||||
*/
|
||||
public double getDouble(int index) throws JSONException {
|
||||
Object o = get(index);
|
||||
try {
|
||||
return o instanceof Number ?
|
||||
((Number)o).doubleValue() :
|
||||
Double.valueOf((String)o).doubleValue();
|
||||
} catch (Exception e) {
|
||||
throw new JSONException("JSONArray[" + index +
|
||||
"] is not a number.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the int value associated with an index.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return The value.
|
||||
* @throws JSONException If the key is not found or if the value cannot
|
||||
* be converted to a number.
|
||||
* if the value cannot be converted to a number.
|
||||
*/
|
||||
public int getInt(int index) throws JSONException {
|
||||
Object o = get(index);
|
||||
return o instanceof Number ?
|
||||
((Number)o).intValue() : (int)getDouble(index);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the JSONArray associated with an index.
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return A JSONArray value.
|
||||
* @throws JSONException If there is no value for the index. or if the
|
||||
* value is not a JSONArray
|
||||
*/
|
||||
public JSONArray getJSONArray(int index) throws JSONException {
|
||||
Object o = get(index);
|
||||
if (o instanceof JSONArray) {
|
||||
return (JSONArray)o;
|
||||
}
|
||||
throw new JSONException("JSONArray[" + index +
|
||||
"] is not a JSONArray.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the JSONObject associated with an index.
|
||||
* @param index subscript
|
||||
* @return A JSONObject value.
|
||||
* @throws JSONException If there is no value for the index or if the
|
||||
* value is not a JSONObject
|
||||
*/
|
||||
public JSONObject getJSONObject(int index) throws JSONException {
|
||||
Object o = get(index);
|
||||
if (o instanceof JSONObject) {
|
||||
return (JSONObject)o;
|
||||
}
|
||||
throw new JSONException("JSONArray[" + index +
|
||||
"] is not a JSONObject.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the long value associated with an index.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return The value.
|
||||
* @throws JSONException If the key is not found or if the value cannot
|
||||
* be converted to a number.
|
||||
*/
|
||||
public long getLong(int index) throws JSONException {
|
||||
Object o = get(index);
|
||||
return o instanceof Number ?
|
||||
((Number)o).longValue() : (long)getDouble(index);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the string associated with an index.
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return A string value.
|
||||
* @throws JSONException If there is no value for the index.
|
||||
*/
|
||||
public String getString(int index) throws JSONException {
|
||||
return get(index).toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the value is null.
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return true if the value at the index is null, or if there is no value.
|
||||
*/
|
||||
public boolean isNull(int index) {
|
||||
return JSONObject.NULL.equals(opt(index));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a string from the contents of this JSONArray. The
|
||||
* <code>separator</code> string is inserted between each element.
|
||||
* Warning: This method assumes that the data structure is acyclical.
|
||||
* @param separator A string that will be inserted between the elements.
|
||||
* @return a string.
|
||||
* @throws JSONException If the array contains an invalid number.
|
||||
*/
|
||||
public String join(String separator) throws JSONException {
|
||||
int len = length();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
for (int i = 0; i < len; i += 1) {
|
||||
if (i > 0) {
|
||||
sb.append(separator);
|
||||
}
|
||||
sb.append(JSONObject.valueToString(this.myArrayList.get(i)));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the number of elements in the JSONArray, included nulls.
|
||||
*
|
||||
* @return The length (or size).
|
||||
*/
|
||||
public int length() {
|
||||
return this.myArrayList.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional object value associated with an index.
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return An object value, or null if there is no
|
||||
* object at that index.
|
||||
*/
|
||||
public Object opt(int index) {
|
||||
return (index < 0 || index >= length()) ?
|
||||
null : this.myArrayList.get(index);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional boolean value associated with an index.
|
||||
* It returns false if there is no value at that index,
|
||||
* or if the value is not Boolean.TRUE or the String "true".
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return The truth.
|
||||
*/
|
||||
public boolean optBoolean(int index) {
|
||||
return optBoolean(index, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional boolean value associated with an index.
|
||||
* It returns the defaultValue if there is no value at that index or if
|
||||
* it is not a Boolean or the String "true" or "false" (case insensitive).
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @param defaultValue A boolean default.
|
||||
* @return The truth.
|
||||
*/
|
||||
public boolean optBoolean(int index, boolean defaultValue) {
|
||||
try {
|
||||
return getBoolean(index);
|
||||
} catch (Exception e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional double value associated with an index.
|
||||
* NaN is returned if there is no value for the index,
|
||||
* or if the value is not a number and cannot be converted to a number.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return The value.
|
||||
*/
|
||||
public double optDouble(int index) {
|
||||
return optDouble(index, Double.NaN);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional double value associated with an index.
|
||||
* The defaultValue is returned if there is no value for the index,
|
||||
* or if the value is not a number and cannot be converted to a number.
|
||||
*
|
||||
* @param index subscript
|
||||
* @param defaultValue The default value.
|
||||
* @return The value.
|
||||
*/
|
||||
public double optDouble(int index, double defaultValue) {
|
||||
try {
|
||||
return getDouble(index);
|
||||
} catch (Exception e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional int value associated with an index.
|
||||
* Zero is returned if there is no value for the index,
|
||||
* or if the value is not a number and cannot be converted to a number.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return The value.
|
||||
*/
|
||||
public int optInt(int index) {
|
||||
return optInt(index, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional int value associated with an index.
|
||||
* The defaultValue is returned if there is no value for the index,
|
||||
* or if the value is not a number and cannot be converted to a number.
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @param defaultValue The default value.
|
||||
* @return The value.
|
||||
*/
|
||||
public int optInt(int index, int defaultValue) {
|
||||
try {
|
||||
return getInt(index);
|
||||
} catch (Exception e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional JSONArray associated with an index.
|
||||
* @param index subscript
|
||||
* @return A JSONArray value, or null if the index has no value,
|
||||
* or if the value is not a JSONArray.
|
||||
*/
|
||||
public JSONArray optJSONArray(int index) {
|
||||
Object o = opt(index);
|
||||
return o instanceof JSONArray ? (JSONArray)o : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional JSONObject associated with an index.
|
||||
* Null is returned if the key is not found, or null if the index has
|
||||
* no value, or if the value is not a JSONObject.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return A JSONObject value.
|
||||
*/
|
||||
public JSONObject optJSONObject(int index) {
|
||||
Object o = opt(index);
|
||||
return o instanceof JSONObject ? (JSONObject)o : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional long value associated with an index.
|
||||
* Zero is returned if there is no value for the index,
|
||||
* or if the value is not a number and cannot be converted to a number.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return The value.
|
||||
*/
|
||||
public long optLong(int index) {
|
||||
return optLong(index, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional long value associated with an index.
|
||||
* The defaultValue is returned if there is no value for the index,
|
||||
* or if the value is not a number and cannot be converted to a number.
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @param defaultValue The default value.
|
||||
* @return The value.
|
||||
*/
|
||||
public long optLong(int index, long defaultValue) {
|
||||
try {
|
||||
return getLong(index);
|
||||
} catch (Exception e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional string value associated with an index. It returns an
|
||||
* empty string if there is no value at that index. If the value
|
||||
* is not a string and is not null, then it is coverted to a string.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @return A String value.
|
||||
*/
|
||||
public String optString(int index) {
|
||||
return optString(index, "");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the optional string associated with an index.
|
||||
* The defaultValue is returned if the key is not found.
|
||||
*
|
||||
* @param index The index must be between 0 and length() - 1.
|
||||
* @param defaultValue The default value.
|
||||
* @return A String value.
|
||||
*/
|
||||
public String optString(int index, String defaultValue) {
|
||||
Object o = opt(index);
|
||||
return o != null ? o.toString() : defaultValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append a boolean value. This increases the array's length by one.
|
||||
*
|
||||
* @param value A boolean value.
|
||||
* @return this.
|
||||
*/
|
||||
public JSONArray put(boolean value) {
|
||||
put(value ? Boolean.TRUE : Boolean.FALSE);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put a value in the JSONArray, where the value will be a
|
||||
* JSONArray which is produced from a Collection.
|
||||
* @param value A Collection value.
|
||||
* @return this.
|
||||
*/
|
||||
public JSONArray put(Collection value) {
|
||||
put(new JSONArray(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append a double value. This increases the array's length by one.
|
||||
*
|
||||
* @param value A double value.
|
||||
* @throws JSONException if the value is not finite.
|
||||
* @return this.
|
||||
*/
|
||||
public JSONArray put(double value) throws JSONException {
|
||||
Double d = new Double(value);
|
||||
JSONObject.testValidity(d);
|
||||
put(d);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append an int value. This increases the array's length by one.
|
||||
*
|
||||
* @param value An int value.
|
||||
* @return this.
|
||||
*/
|
||||
public JSONArray put(int value) {
|
||||
put(new Integer(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append an long value. This increases the array's length by one.
|
||||
*
|
||||
* @param value A long value.
|
||||
* @return this.
|
||||
*/
|
||||
public JSONArray put(long value) {
|
||||
put(new Long(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put a value in the JSONArray, where the value will be a
|
||||
* JSONObject which is produced from a Map.
|
||||
* @param value A Map value.
|
||||
* @return this.
|
||||
*/
|
||||
public JSONArray put(Map value) {
|
||||
put(new JSONObject(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append an object value. This increases the array's length by one.
|
||||
* @param value An object value. The value should be a
|
||||
* Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the
|
||||
* JSONObject.NULL object.
|
||||
* @return this.
|
||||
*/
|
||||
public JSONArray put(Object value) {
|
||||
this.myArrayList.add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put or replace a boolean value in the JSONArray. If the index is greater
|
||||
* than the length of the JSONArray, then null elements will be added as
|
||||
* necessary to pad it out.
|
||||
* @param index The subscript.
|
||||
* @param value A boolean value.
|
||||
* @return this.
|
||||
* @throws JSONException If the index is negative.
|
||||
*/
|
||||
public JSONArray put(int index, boolean value) throws JSONException {
|
||||
put(index, value ? Boolean.TRUE : Boolean.FALSE);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put a value in the JSONArray, where the value will be a
|
||||
* JSONArray which is produced from a Collection.
|
||||
* @param index The subscript.
|
||||
* @param value A Collection value.
|
||||
* @return this.
|
||||
* @throws JSONException If the index is negative or if the value is
|
||||
* not finite.
|
||||
*/
|
||||
public JSONArray put(int index, Collection value) throws JSONException {
|
||||
put(index, new JSONArray(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put or replace a double value. If the index is greater than the length of
|
||||
* the JSONArray, then null elements will be added as necessary to pad
|
||||
* it out.
|
||||
* @param index The subscript.
|
||||
* @param value A double value.
|
||||
* @return this.
|
||||
* @throws JSONException If the index is negative or if the value is
|
||||
* not finite.
|
||||
*/
|
||||
public JSONArray put(int index, double value) throws JSONException {
|
||||
put(index, new Double(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put or replace an int value. If the index is greater than the length of
|
||||
* the JSONArray, then null elements will be added as necessary to pad
|
||||
* it out.
|
||||
* @param index The subscript.
|
||||
* @param value An int value.
|
||||
* @return this.
|
||||
* @throws JSONException If the index is negative.
|
||||
*/
|
||||
public JSONArray put(int index, int value) throws JSONException {
|
||||
put(index, new Integer(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put or replace a long value. If the index is greater than the length of
|
||||
* the JSONArray, then null elements will be added as necessary to pad
|
||||
* it out.
|
||||
* @param index The subscript.
|
||||
* @param value A long value.
|
||||
* @return this.
|
||||
* @throws JSONException If the index is negative.
|
||||
*/
|
||||
public JSONArray put(int index, long value) throws JSONException {
|
||||
put(index, new Long(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put a value in the JSONArray, where the value will be a
|
||||
* JSONObject which is produced from a Map.
|
||||
* @param index The subscript.
|
||||
* @param value The Map value.
|
||||
* @return this.
|
||||
* @throws JSONException If the index is negative or if the the value is
|
||||
* an invalid number.
|
||||
*/
|
||||
public JSONArray put(int index, Map value) throws JSONException {
|
||||
put(index, new JSONObject(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put or replace an object value in the JSONArray. If the index is greater
|
||||
* than the length of the JSONArray, then null elements will be added as
|
||||
* necessary to pad it out.
|
||||
* @param index The subscript.
|
||||
* @param value The value to put into the array. The value should be a
|
||||
* Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the
|
||||
* JSONObject.NULL object.
|
||||
* @return this.
|
||||
* @throws JSONException If the index is negative or if the the value is
|
||||
* an invalid number.
|
||||
*/
|
||||
public JSONArray put(int index, Object value) throws JSONException {
|
||||
JSONObject.testValidity(value);
|
||||
if (index < 0) {
|
||||
throw new JSONException("JSONArray[" + index + "] not found.");
|
||||
}
|
||||
if (index < length()) {
|
||||
this.myArrayList.set(index, value);
|
||||
} else {
|
||||
while (index != length()) {
|
||||
put(JSONObject.NULL);
|
||||
}
|
||||
put(value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove an index and close the hole.
|
||||
* @param index The index of the element to be removed.
|
||||
* @return The value that was associated with the index,
|
||||
* or null if there was no value.
|
||||
*/
|
||||
public Object remove(int index) {
|
||||
Object o = opt(index);
|
||||
this.myArrayList.remove(index);
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Produce a JSONObject by combining a JSONArray of names with the values
|
||||
* of this JSONArray.
|
||||
* @param names A JSONArray containing a list of key strings. These will be
|
||||
* paired with the values.
|
||||
* @return A JSONObject, or null if there are no names or if this JSONArray
|
||||
* has no values.
|
||||
* @throws JSONException If any of the names are null.
|
||||
*/
|
||||
public JSONObject toJSONObject(JSONArray names) throws JSONException {
|
||||
if (names == null || names.length() == 0 || length() == 0) {
|
||||
return null;
|
||||
}
|
||||
JSONObject jo = new JSONObject();
|
||||
for (int i = 0; i < names.length(); i += 1) {
|
||||
jo.put(names.getString(i), this.opt(i));
|
||||
}
|
||||
return jo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a JSON text of this JSONArray. For compactness, no
|
||||
* unnecessary whitespace is added. If it is not possible to produce a
|
||||
* syntactically correct JSON text then null will be returned instead. This
|
||||
* could occur if the array contains an invalid number.
|
||||
* <p>
|
||||
* Warning: This method assumes that the data structure is acyclical.
|
||||
*
|
||||
* @return a printable, displayable, transmittable
|
||||
* representation of the array.
|
||||
*/
|
||||
public String toString() {
|
||||
try {
|
||||
return '[' + join(",") + ']';
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a prettyprinted JSON text of this JSONArray.
|
||||
* Warning: This method assumes that the data structure is acyclical.
|
||||
* @param indentFactor The number of spaces to add to each level of
|
||||
* indentation.
|
||||
* @return a printable, displayable, transmittable
|
||||
* representation of the object, beginning
|
||||
* with <code>[</code> <small>(left bracket)</small> and ending
|
||||
* with <code>]</code> <small>(right bracket)</small>.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public String toString(int indentFactor) throws JSONException {
|
||||
return toString(indentFactor, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a prettyprinted JSON text of this JSONArray.
|
||||
* Warning: This method assumes that the data structure is acyclical.
|
||||
* @param indentFactor The number of spaces to add to each level of
|
||||
* indentation.
|
||||
* @param indent The indention of the top level.
|
||||
* @return a printable, displayable, transmittable
|
||||
* representation of the array.
|
||||
* @throws JSONException
|
||||
*/
|
||||
String toString(int indentFactor, int indent) throws JSONException {
|
||||
int len = length();
|
||||
if (len == 0) {
|
||||
return "[]";
|
||||
}
|
||||
int i;
|
||||
StringBuffer sb = new StringBuffer("[");
|
||||
if (len == 1) {
|
||||
sb.append(JSONObject.valueToString(this.myArrayList.get(0),
|
||||
indentFactor, indent));
|
||||
} else {
|
||||
int newindent = indent + indentFactor;
|
||||
sb.append('\n');
|
||||
for (i = 0; i < len; i += 1) {
|
||||
if (i > 0) {
|
||||
sb.append(",\n");
|
||||
}
|
||||
for (int j = 0; j < newindent; j += 1) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(JSONObject.valueToString(this.myArrayList.get(i),
|
||||
indentFactor, newindent));
|
||||
}
|
||||
sb.append('\n');
|
||||
for (i = 0; i < indent; i += 1) {
|
||||
sb.append(' ');
|
||||
}
|
||||
}
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write the contents of the JSONArray as JSON text to a writer.
|
||||
* For compactness, no whitespace is added.
|
||||
* <p>
|
||||
* Warning: This method assumes that the data structure is acyclical.
|
||||
*
|
||||
* @return The writer.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public Writer write(Writer writer) throws JSONException {
|
||||
try {
|
||||
boolean b = false;
|
||||
int len = length();
|
||||
|
||||
writer.write('[');
|
||||
|
||||
for (int i = 0; i < len; i += 1) {
|
||||
if (b) {
|
||||
writer.write(',');
|
||||
}
|
||||
Object v = this.myArrayList.get(i);
|
||||
if (v instanceof JSONObject) {
|
||||
((JSONObject)v).write(writer);
|
||||
} else if (v instanceof JSONArray) {
|
||||
((JSONArray)v).write(writer);
|
||||
} else {
|
||||
writer.write(JSONObject.valueToString(v));
|
||||
}
|
||||
b = true;
|
||||
}
|
||||
writer.write(']');
|
||||
return writer;
|
||||
} catch (IOException e) {
|
||||
throw new JSONException(e);
|
||||
}
|
||||
}
|
||||
}
|
31
src/main/java/org/json/JSONException.java
Normal file
31
src/main/java/org/json/JSONException.java
Normal file
@ -0,0 +1,31 @@
|
||||
package org.json;
|
||||
|
||||
/**
|
||||
* The JSONException is thrown by the JSON.org classes when things are amiss.
|
||||
* @author JSON.org
|
||||
* @version 2008-09-18
|
||||
*/
|
||||
public class JSONException extends Exception {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 0;
|
||||
private Throwable cause;
|
||||
|
||||
/**
|
||||
* Constructs a JSONException with an explanatory message.
|
||||
* @param message Detail about the reason for the exception.
|
||||
*/
|
||||
public JSONException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public JSONException(Throwable t) {
|
||||
super(t.getMessage());
|
||||
this.cause = t;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return this.cause;
|
||||
}
|
||||
}
|
455
src/main/java/org/json/JSONML.java
Normal file
455
src/main/java/org/json/JSONML.java
Normal file
@ -0,0 +1,455 @@
|
||||
package org.json;
|
||||
|
||||
/*
|
||||
Copyright (c) 2008 JSON.org
|
||||
|
||||
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 shall be used for Good, not Evil.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
/**
|
||||
* This provides static methods to convert an XML text into a JSONArray or
|
||||
* JSONObject, and to covert a JSONArray or JSONObject into an XML text using
|
||||
* the JsonML transform.
|
||||
* @author JSON.org
|
||||
* @version 2010-02-12
|
||||
*/
|
||||
public class JSONML {
|
||||
|
||||
/**
|
||||
* Parse XML values and store them in a JSONArray.
|
||||
* @param x The XMLTokener containing the source string.
|
||||
* @param arrayForm true if array form, false if object form.
|
||||
* @param ja The JSONArray that is containing the current tag or null
|
||||
* if we are at the outermost level.
|
||||
* @return A JSONArray if the value is the outermost tag, otherwise null.
|
||||
* @throws JSONException
|
||||
*/
|
||||
private static Object parse(XMLTokener x, boolean arrayForm,
|
||||
JSONArray ja) throws JSONException {
|
||||
String attribute;
|
||||
char c;
|
||||
String closeTag = null;
|
||||
int i;
|
||||
JSONArray newja = null;
|
||||
JSONObject newjo = null;
|
||||
Object token;
|
||||
String tagName = null;
|
||||
|
||||
// Test for and skip past these forms:
|
||||
// <!-- ... -->
|
||||
// <![ ... ]]>
|
||||
// <! ... >
|
||||
// <? ... ?>
|
||||
|
||||
while (true) {
|
||||
token = x.nextContent();
|
||||
if (token == XML.LT) {
|
||||
token = x.nextToken();
|
||||
if (token instanceof Character) {
|
||||
if (token == XML.SLASH) {
|
||||
|
||||
// Close tag </
|
||||
|
||||
token = x.nextToken();
|
||||
if (!(token instanceof String)) {
|
||||
throw new JSONException(
|
||||
"Expected a closing name instead of '" +
|
||||
token + "'.");
|
||||
}
|
||||
if (x.nextToken() != XML.GT) {
|
||||
throw x.syntaxError("Misshaped close tag");
|
||||
}
|
||||
return token;
|
||||
} else if (token == XML.BANG) {
|
||||
|
||||
// <!
|
||||
|
||||
c = x.next();
|
||||
if (c == '-') {
|
||||
if (x.next() == '-') {
|
||||
x.skipPast("-->");
|
||||
}
|
||||
x.back();
|
||||
} else if (c == '[') {
|
||||
token = x.nextToken();
|
||||
if (token.equals("CDATA") && x.next() == '[') {
|
||||
if (ja != null) {
|
||||
ja.put(x.nextCDATA());
|
||||
}
|
||||
} else {
|
||||
throw x.syntaxError("Expected 'CDATA['");
|
||||
}
|
||||
} else {
|
||||
i = 1;
|
||||
do {
|
||||
token = x.nextMeta();
|
||||
if (token == null) {
|
||||
throw x.syntaxError("Missing '>' after '<!'.");
|
||||
} else if (token == XML.LT) {
|
||||
i += 1;
|
||||
} else if (token == XML.GT) {
|
||||
i -= 1;
|
||||
}
|
||||
} while (i > 0);
|
||||
}
|
||||
} else if (token == XML.QUEST) {
|
||||
|
||||
// <?
|
||||
|
||||
x.skipPast("?>");
|
||||
} else {
|
||||
throw x.syntaxError("Misshaped tag");
|
||||
}
|
||||
|
||||
// Open tag <
|
||||
|
||||
} else {
|
||||
if (!(token instanceof String)) {
|
||||
throw x.syntaxError("Bad tagName '" + token + "'.");
|
||||
}
|
||||
tagName = (String)token;
|
||||
newja = new JSONArray();
|
||||
newjo = new JSONObject();
|
||||
if (arrayForm) {
|
||||
newja.put(tagName);
|
||||
if (ja != null) {
|
||||
ja.put(newja);
|
||||
}
|
||||
} else {
|
||||
newjo.put("tagName", tagName);
|
||||
if (ja != null) {
|
||||
ja.put(newjo);
|
||||
}
|
||||
}
|
||||
token = null;
|
||||
for (;;) {
|
||||
if (token == null) {
|
||||
token = x.nextToken();
|
||||
}
|
||||
if (token == null) {
|
||||
throw x.syntaxError("Misshaped tag");
|
||||
}
|
||||
if (!(token instanceof String)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// attribute = value
|
||||
|
||||
attribute = (String)token;
|
||||
if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) {
|
||||
throw x.syntaxError("Reserved attribute.");
|
||||
}
|
||||
token = x.nextToken();
|
||||
if (token == XML.EQ) {
|
||||
token = x.nextToken();
|
||||
if (!(token instanceof String)) {
|
||||
throw x.syntaxError("Missing value");
|
||||
}
|
||||
newjo.accumulate(attribute, JSONObject.stringToValue((String)token));
|
||||
token = null;
|
||||
} else {
|
||||
newjo.accumulate(attribute, "");
|
||||
}
|
||||
}
|
||||
if (arrayForm && newjo.length() > 0) {
|
||||
newja.put(newjo);
|
||||
}
|
||||
|
||||
// Empty tag <.../>
|
||||
|
||||
if (token == XML.SLASH) {
|
||||
if (x.nextToken() != XML.GT) {
|
||||
throw x.syntaxError("Misshaped tag");
|
||||
}
|
||||
if (ja == null) {
|
||||
if (arrayForm) {
|
||||
return newja;
|
||||
} else {
|
||||
return newjo;
|
||||
}
|
||||
}
|
||||
|
||||
// Content, between <...> and </...>
|
||||
|
||||
} else {
|
||||
if (token != XML.GT) {
|
||||
throw x.syntaxError("Misshaped tag");
|
||||
}
|
||||
closeTag = (String)parse(x, arrayForm, newja);
|
||||
if (closeTag != null) {
|
||||
if (!closeTag.equals(tagName)) {
|
||||
throw x.syntaxError("Mismatched '" + tagName +
|
||||
"' and '" + closeTag + "'");
|
||||
}
|
||||
tagName = null;
|
||||
if (!arrayForm && newja.length() > 0) {
|
||||
newjo.put("childNodes", newja);
|
||||
}
|
||||
if (ja == null) {
|
||||
if (arrayForm) {
|
||||
return newja;
|
||||
} else {
|
||||
return newjo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ja != null) {
|
||||
ja.put(token instanceof String ?
|
||||
JSONObject.stringToValue((String)token) : token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a well-formed (but not necessarily valid) XML string into a
|
||||
* JSONArray using the JsonML transform. Each XML tag is represented as
|
||||
* a JSONArray in which the first element is the tag name. If the tag has
|
||||
* attributes, then the second element will be JSONObject containing the
|
||||
* name/value pairs. If the tag contains children, then strings and
|
||||
* JSONArrays will represent the child tags.
|
||||
* Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
||||
* @param string The source string.
|
||||
* @return A JSONArray containing the structured data from the XML string.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONArray toJSONArray(String string) throws JSONException {
|
||||
return toJSONArray(new XMLTokener(string));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a well-formed (but not necessarily valid) XML string into a
|
||||
* JSONArray using the JsonML transform. Each XML tag is represented as
|
||||
* a JSONArray in which the first element is the tag name. If the tag has
|
||||
* attributes, then the second element will be JSONObject containing the
|
||||
* name/value pairs. If the tag contains children, then strings and
|
||||
* JSONArrays will represent the child content and tags.
|
||||
* Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
||||
* @param x An XMLTokener.
|
||||
* @return A JSONArray containing the structured data from the XML string.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
|
||||
return (JSONArray)parse(x, true, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Convert a well-formed (but not necessarily valid) XML string into a
|
||||
* JSONObject using the JsonML transform. Each XML tag is represented as
|
||||
* a JSONObject with a "tagName" property. If the tag has attributes, then
|
||||
* the attributes will be in the JSONObject as properties. If the tag
|
||||
* contains children, the object will have a "childNodes" property which
|
||||
* will be an array of strings and JsonML JSONObjects.
|
||||
|
||||
* Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
||||
* @param x An XMLTokener of the XML source text.
|
||||
* @return A JSONObject containing the structured data from the XML string.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
|
||||
return (JSONObject)parse(x, false, null);
|
||||
}
|
||||
/**
|
||||
* Convert a well-formed (but not necessarily valid) XML string into a
|
||||
* JSONObject using the JsonML transform. Each XML tag is represented as
|
||||
* a JSONObject with a "tagName" property. If the tag has attributes, then
|
||||
* the attributes will be in the JSONObject as properties. If the tag
|
||||
* contains children, the object will have a "childNodes" property which
|
||||
* will be an array of strings and JsonML JSONObjects.
|
||||
|
||||
* Comments, prologs, DTDs, and <code><[ [ ]]></code> are ignored.
|
||||
* @param string The XML source text.
|
||||
* @return A JSONObject containing the structured data from the XML string.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONObject toJSONObject(String string) throws JSONException {
|
||||
return toJSONObject(new XMLTokener(string));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reverse the JSONML transformation, making an XML text from a JSONArray.
|
||||
* @param ja A JSONArray.
|
||||
* @return An XML string.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static String toString(JSONArray ja) throws JSONException {
|
||||
Object e;
|
||||
int i;
|
||||
JSONObject jo;
|
||||
String k;
|
||||
Iterator keys;
|
||||
int length;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
String tagName;
|
||||
String v;
|
||||
|
||||
// Emit <tagName
|
||||
|
||||
tagName = ja.getString(0);
|
||||
XML.noSpace(tagName);
|
||||
tagName = XML.escape(tagName);
|
||||
sb.append('<');
|
||||
sb.append(tagName);
|
||||
|
||||
e = ja.opt(1);
|
||||
if (e instanceof JSONObject) {
|
||||
i = 2;
|
||||
jo = (JSONObject)e;
|
||||
|
||||
// Emit the attributes
|
||||
|
||||
keys = jo.keys();
|
||||
while (keys.hasNext()) {
|
||||
k = keys.next().toString();
|
||||
XML.noSpace(k);
|
||||
v = jo.optString(k);
|
||||
if (v != null) {
|
||||
sb.append(' ');
|
||||
sb.append(XML.escape(k));
|
||||
sb.append('=');
|
||||
sb.append('"');
|
||||
sb.append(XML.escape(v));
|
||||
sb.append('"');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
i = 1;
|
||||
}
|
||||
|
||||
//Emit content in body
|
||||
|
||||
length = ja.length();
|
||||
if (i >= length) {
|
||||
sb.append('/');
|
||||
sb.append('>');
|
||||
} else {
|
||||
sb.append('>');
|
||||
do {
|
||||
e = ja.get(i);
|
||||
i += 1;
|
||||
if (e != null) {
|
||||
if (e instanceof String) {
|
||||
sb.append(XML.escape(e.toString()));
|
||||
} else if (e instanceof JSONObject) {
|
||||
sb.append(toString((JSONObject)e));
|
||||
} else if (e instanceof JSONArray) {
|
||||
sb.append(toString((JSONArray)e));
|
||||
}
|
||||
}
|
||||
} while (i < length);
|
||||
sb.append('<');
|
||||
sb.append('/');
|
||||
sb.append(tagName);
|
||||
sb.append('>');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the JSONML transformation, making an XML text from a JSONObject.
|
||||
* The JSONObject must contain a "tagName" property. If it has children,
|
||||
* then it must have a "childNodes" property containing an array of objects.
|
||||
* The other properties are attributes with string values.
|
||||
* @param jo A JSONObject.
|
||||
* @return An XML string.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static String toString(JSONObject jo) throws JSONException {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
Object e;
|
||||
int i;
|
||||
JSONArray ja;
|
||||
String k;
|
||||
Iterator keys;
|
||||
int len;
|
||||
String tagName;
|
||||
String v;
|
||||
|
||||
//Emit <tagName
|
||||
|
||||
tagName = jo.optString("tagName");
|
||||
if (tagName == null) {
|
||||
return XML.escape(jo.toString());
|
||||
}
|
||||
XML.noSpace(tagName);
|
||||
tagName = XML.escape(tagName);
|
||||
sb.append('<');
|
||||
sb.append(tagName);
|
||||
|
||||
//Emit the attributes
|
||||
|
||||
keys = jo.keys();
|
||||
while (keys.hasNext()) {
|
||||
k = keys.next().toString();
|
||||
if (!k.equals("tagName") && !k.equals("childNodes")) {
|
||||
XML.noSpace(k);
|
||||
v = jo.optString(k);
|
||||
if (v != null) {
|
||||
sb.append(' ');
|
||||
sb.append(XML.escape(k));
|
||||
sb.append('=');
|
||||
sb.append('"');
|
||||
sb.append(XML.escape(v));
|
||||
sb.append('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Emit content in body
|
||||
|
||||
ja = jo.optJSONArray("childNodes");
|
||||
if (ja == null) {
|
||||
sb.append('/');
|
||||
sb.append('>');
|
||||
} else {
|
||||
sb.append('>');
|
||||
len = ja.length();
|
||||
for (i = 0; i < len; i += 1) {
|
||||
e = ja.get(i);
|
||||
if (e != null) {
|
||||
if (e instanceof String) {
|
||||
sb.append(XML.escape(e.toString()));
|
||||
} else if (e instanceof JSONObject) {
|
||||
sb.append(toString((JSONObject)e));
|
||||
} else if (e instanceof JSONArray) {
|
||||
sb.append(toString((JSONArray)e));
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append('<');
|
||||
sb.append('/');
|
||||
sb.append(tagName);
|
||||
sb.append('>');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
1583
src/main/java/org/json/JSONObject.java
Normal file
1583
src/main/java/org/json/JSONObject.java
Normal file
File diff suppressed because it is too large
Load Diff
18
src/main/java/org/json/JSONString.java
Normal file
18
src/main/java/org/json/JSONString.java
Normal file
@ -0,0 +1,18 @@
|
||||
package org.json;
|
||||
/**
|
||||
* The <code>JSONString</code> interface allows a <code>toJSONString()</code>
|
||||
* method so that a class can change the behavior of
|
||||
* <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>,
|
||||
* and <code>JSONWriter.value(</code>Object<code>)</code>. The
|
||||
* <code>toJSONString</code> method will be used instead of the default behavior
|
||||
* of using the Object's <code>toString()</code> method and quoting the result.
|
||||
*/
|
||||
public interface JSONString {
|
||||
/**
|
||||
* The <code>toJSONString</code> method allows a class to produce its own JSON
|
||||
* serialization.
|
||||
*
|
||||
* @return A strictly syntactically correct JSON text.
|
||||
*/
|
||||
public String toJSONString();
|
||||
}
|
78
src/main/java/org/json/JSONStringer.java
Normal file
78
src/main/java/org/json/JSONStringer.java
Normal file
@ -0,0 +1,78 @@
|
||||
package org.json;
|
||||
|
||||
/*
|
||||
Copyright (c) 2006 JSON.org
|
||||
|
||||
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 shall be used for Good, not Evil.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* JSONStringer provides a quick and convenient way of producing JSON text.
|
||||
* The texts produced strictly conform to JSON syntax rules. No whitespace is
|
||||
* added, so the results are ready for transmission or storage. Each instance of
|
||||
* JSONStringer can produce one JSON text.
|
||||
* <p>
|
||||
* A JSONStringer instance provides a <code>value</code> method for appending
|
||||
* values to the
|
||||
* text, and a <code>key</code>
|
||||
* method for adding keys before values in objects. There are <code>array</code>
|
||||
* and <code>endArray</code> methods that make and bound array values, and
|
||||
* <code>object</code> and <code>endObject</code> methods which make and bound
|
||||
* object values. All of these methods return the JSONWriter instance,
|
||||
* permitting cascade style. For example, <pre>
|
||||
* myString = new JSONStringer()
|
||||
* .object()
|
||||
* .key("JSON")
|
||||
* .value("Hello, World!")
|
||||
* .endObject()
|
||||
* .toString();</pre> which produces the string <pre>
|
||||
* {"JSON":"Hello, World!"}</pre>
|
||||
* <p>
|
||||
* The first method called must be <code>array</code> or <code>object</code>.
|
||||
* There are no methods for adding commas or colons. JSONStringer adds them for
|
||||
* you. Objects and arrays can be nested up to 20 levels deep.
|
||||
* <p>
|
||||
* This can sometimes be easier than using a JSONObject to build a string.
|
||||
* @author JSON.org
|
||||
* @version 2008-09-18
|
||||
*/
|
||||
public class JSONStringer extends JSONWriter {
|
||||
/**
|
||||
* Make a fresh JSONStringer. It can be used to build one JSON text.
|
||||
*/
|
||||
public JSONStringer() {
|
||||
super(new StringWriter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JSON text. This method is used to obtain the product of the
|
||||
* JSONStringer instance. It will return <code>null</code> if there was a
|
||||
* problem in the construction of the JSON text (such as the calls to
|
||||
* <code>array</code> were not properly balanced with calls to
|
||||
* <code>endArray</code>).
|
||||
* @return The JSON text.
|
||||
*/
|
||||
public String toString() {
|
||||
return this.mode == 'd' ? this.writer.toString() : null;
|
||||
}
|
||||
}
|
435
src/main/java/org/json/JSONTokener.java
Normal file
435
src/main/java/org/json/JSONTokener.java
Normal file
@ -0,0 +1,435 @@
|
||||
package org.json;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
/*
|
||||
Copyright (c) 2002 JSON.org
|
||||
|
||||
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 shall be used for Good, not Evil.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A JSONTokener takes a source string and extracts characters and tokens from
|
||||
* it. It is used by the JSONObject and JSONArray constructors to parse
|
||||
* JSON source strings.
|
||||
* @author JSON.org
|
||||
* @version 2010-02-02
|
||||
*/
|
||||
public class JSONTokener {
|
||||
|
||||
private int character;
|
||||
private boolean eof;
|
||||
private int index;
|
||||
private int line;
|
||||
private char previous;
|
||||
private Reader reader;
|
||||
private boolean usePrevious;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a JSONTokener from a reader.
|
||||
*
|
||||
* @param reader A reader.
|
||||
*/
|
||||
public JSONTokener(Reader reader) {
|
||||
this.reader = reader.markSupported() ?
|
||||
reader : new BufferedReader(reader);
|
||||
this.eof = false;
|
||||
this.usePrevious = false;
|
||||
this.previous = 0;
|
||||
this.index = 0;
|
||||
this.character = 1;
|
||||
this.line = 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a JSONTokener from a string.
|
||||
*
|
||||
* @param s A source string.
|
||||
*/
|
||||
public JSONTokener(String s) {
|
||||
this(new StringReader(s));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Back up one character. This provides a sort of lookahead capability,
|
||||
* so that you can test for a digit or letter before attempting to parse
|
||||
* the next number or identifier.
|
||||
*/
|
||||
public void back() throws JSONException {
|
||||
if (usePrevious || index <= 0) {
|
||||
throw new JSONException("Stepping back two steps is not supported");
|
||||
}
|
||||
this.index -= 1;
|
||||
this.character -= 1;
|
||||
this.usePrevious = true;
|
||||
this.eof = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the hex value of a character (base16).
|
||||
* @param c A character between '0' and '9' or between 'A' and 'F' or
|
||||
* between 'a' and 'f'.
|
||||
* @return An int between 0 and 15, or -1 if c was not a hex digit.
|
||||
*/
|
||||
public static int dehexchar(char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return c - ('A' - 10);
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - ('a' - 10);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean end() {
|
||||
return eof && !usePrevious;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the source string still contains characters that next()
|
||||
* can consume.
|
||||
* @return true if not yet at the end of the source.
|
||||
*/
|
||||
public boolean more() throws JSONException {
|
||||
next();
|
||||
if (end()) {
|
||||
return false;
|
||||
}
|
||||
back();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next character in the source string.
|
||||
*
|
||||
* @return The next character, or 0 if past the end of the source string.
|
||||
*/
|
||||
public char next() throws JSONException {
|
||||
int c;
|
||||
if (this.usePrevious) {
|
||||
this.usePrevious = false;
|
||||
c = this.previous;
|
||||
} else {
|
||||
try {
|
||||
c = this.reader.read();
|
||||
} catch (IOException exception) {
|
||||
throw new JSONException(exception);
|
||||
}
|
||||
|
||||
if (c <= 0) { // End of stream
|
||||
this.eof = true;
|
||||
c = 0;
|
||||
}
|
||||
}
|
||||
this.index += 1;
|
||||
if (this.previous == '\r') {
|
||||
this.line += 1;
|
||||
this.character = c == '\n' ? 0 : 1;
|
||||
} else if (c == '\n') {
|
||||
this.line += 1;
|
||||
this.character = 0;
|
||||
} else {
|
||||
this.character += 1;
|
||||
}
|
||||
this.previous = (char) c;
|
||||
return this.previous;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Consume the next character, and check that it matches a specified
|
||||
* character.
|
||||
* @param c The character to match.
|
||||
* @return The character.
|
||||
* @throws JSONException if the character does not match.
|
||||
*/
|
||||
public char next(char c) throws JSONException {
|
||||
char n = next();
|
||||
if (n != c) {
|
||||
throw syntaxError("Expected '" + c + "' and instead saw '" +
|
||||
n + "'");
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next n characters.
|
||||
*
|
||||
* @param n The number of characters to take.
|
||||
* @return A string of n characters.
|
||||
* @throws JSONException
|
||||
* Substring bounds error if there are not
|
||||
* n characters remaining in the source string.
|
||||
*/
|
||||
public String next(int n) throws JSONException {
|
||||
if (n == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
char[] buffer = new char[n];
|
||||
int pos = 0;
|
||||
|
||||
while (pos < n) {
|
||||
buffer[pos] = next();
|
||||
if (end()) {
|
||||
throw syntaxError("Substring bounds error");
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
return new String(buffer);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next char in the string, skipping whitespace.
|
||||
* @throws JSONException
|
||||
* @return A character, or 0 if there are no more characters.
|
||||
*/
|
||||
public char nextClean() throws JSONException {
|
||||
for (;;) {
|
||||
char c = next();
|
||||
if (c == 0 || c > ' ') {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the characters up to the next close quote character.
|
||||
* Backslash processing is done. The formal JSON format does not
|
||||
* allow strings in single quotes, but an implementation is allowed to
|
||||
* accept them.
|
||||
* @param quote The quoting character, either
|
||||
* <code>"</code> <small>(double quote)</small> or
|
||||
* <code>'</code> <small>(single quote)</small>.
|
||||
* @return A String.
|
||||
* @throws JSONException Unterminated string.
|
||||
*/
|
||||
public String nextString(char quote) throws JSONException {
|
||||
char c;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (;;) {
|
||||
c = next();
|
||||
switch (c) {
|
||||
case 0:
|
||||
case '\n':
|
||||
case '\r':
|
||||
throw syntaxError("Unterminated string");
|
||||
case '\\':
|
||||
c = next();
|
||||
switch (c) {
|
||||
case 'b':
|
||||
sb.append('\b');
|
||||
break;
|
||||
case 't':
|
||||
sb.append('\t');
|
||||
break;
|
||||
case 'n':
|
||||
sb.append('\n');
|
||||
break;
|
||||
case 'f':
|
||||
sb.append('\f');
|
||||
break;
|
||||
case 'r':
|
||||
sb.append('\r');
|
||||
break;
|
||||
case 'u':
|
||||
sb.append((char)Integer.parseInt(next(4), 16));
|
||||
break;
|
||||
case '"':
|
||||
case '\'':
|
||||
case '\\':
|
||||
case '/':
|
||||
sb.append(c);
|
||||
break;
|
||||
default:
|
||||
throw syntaxError("Illegal escape.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (c == quote) {
|
||||
return sb.toString();
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the text up but not including the specified character or the
|
||||
* end of line, whichever comes first.
|
||||
* @param d A delimiter character.
|
||||
* @return A string.
|
||||
*/
|
||||
public String nextTo(char d) throws JSONException {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (;;) {
|
||||
char c = next();
|
||||
if (c == d || c == 0 || c == '\n' || c == '\r') {
|
||||
if (c != 0) {
|
||||
back();
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the text up but not including one of the specified delimiter
|
||||
* characters or the end of line, whichever comes first.
|
||||
* @param delimiters A set of delimiter characters.
|
||||
* @return A string, trimmed.
|
||||
*/
|
||||
public String nextTo(String delimiters) throws JSONException {
|
||||
char c;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (;;) {
|
||||
c = next();
|
||||
if (delimiters.indexOf(c) >= 0 || c == 0 ||
|
||||
c == '\n' || c == '\r') {
|
||||
if (c != 0) {
|
||||
back();
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next value. The value can be a Boolean, Double, Integer,
|
||||
* JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
|
||||
* @throws JSONException If syntax error.
|
||||
*
|
||||
* @return An object.
|
||||
*/
|
||||
public Object nextValue() throws JSONException {
|
||||
char c = nextClean();
|
||||
String s;
|
||||
|
||||
switch (c) {
|
||||
case '"':
|
||||
case '\'':
|
||||
return nextString(c);
|
||||
case '{':
|
||||
back();
|
||||
return new JSONObject(this);
|
||||
case '[':
|
||||
case '(':
|
||||
back();
|
||||
return new JSONArray(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle unquoted text. This could be the values true, false, or
|
||||
* null, or it can be a number. An implementation (such as this one)
|
||||
* is allowed to also accept non-standard forms.
|
||||
*
|
||||
* Accumulate characters until we reach the end of the text or a
|
||||
* formatting character.
|
||||
*/
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
|
||||
sb.append(c);
|
||||
c = next();
|
||||
}
|
||||
back();
|
||||
|
||||
s = sb.toString().trim();
|
||||
if (s.equals("")) {
|
||||
throw syntaxError("Missing value");
|
||||
}
|
||||
return JSONObject.stringToValue(s);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Skip characters until the next character is the requested character.
|
||||
* If the requested character is not found, no characters are skipped.
|
||||
* @param to A character to skip to.
|
||||
* @return The requested character, or zero if the requested character
|
||||
* is not found.
|
||||
*/
|
||||
public char skipTo(char to) throws JSONException {
|
||||
char c;
|
||||
try {
|
||||
int startIndex = this.index;
|
||||
int startCharacter = this.character;
|
||||
int startLine = this.line;
|
||||
reader.mark(Integer.MAX_VALUE);
|
||||
do {
|
||||
c = next();
|
||||
if (c == 0) {
|
||||
reader.reset();
|
||||
this.index = startIndex;
|
||||
this.character = startCharacter;
|
||||
this.line = startLine;
|
||||
return c;
|
||||
}
|
||||
} while (c != to);
|
||||
} catch (IOException exc) {
|
||||
throw new JSONException(exc);
|
||||
}
|
||||
|
||||
back();
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a JSONException to signal a syntax error.
|
||||
*
|
||||
* @param message The error message.
|
||||
* @return A JSONException object, suitable for throwing
|
||||
*/
|
||||
public JSONException syntaxError(String message) {
|
||||
return new JSONException(message + toString());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a printable string of this JSONTokener.
|
||||
*
|
||||
* @return " at {index} [character {character} line {line}]"
|
||||
*/
|
||||
public String toString() {
|
||||
return " at " + index + " [character " + this.character + " line " + this.line + "]";
|
||||
}
|
||||
}
|
323
src/main/java/org/json/JSONWriter.java
Normal file
323
src/main/java/org/json/JSONWriter.java
Normal file
@ -0,0 +1,323 @@
|
||||
package org.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
/*
|
||||
Copyright (c) 2006 JSON.org
|
||||
|
||||
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 shall be used for Good, not Evil.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* JSONWriter provides a quick and convenient way of producing JSON text.
|
||||
* The texts produced strictly conform to JSON syntax rules. No whitespace is
|
||||
* added, so the results are ready for transmission or storage. Each instance of
|
||||
* JSONWriter can produce one JSON text.
|
||||
* <p>
|
||||
* A JSONWriter instance provides a <code>value</code> method for appending
|
||||
* values to the
|
||||
* text, and a <code>key</code>
|
||||
* method for adding keys before values in objects. There are <code>array</code>
|
||||
* and <code>endArray</code> methods that make and bound array values, and
|
||||
* <code>object</code> and <code>endObject</code> methods which make and bound
|
||||
* object values. All of these methods return the JSONWriter instance,
|
||||
* permitting a cascade style. For example, <pre>
|
||||
* new JSONWriter(myWriter)
|
||||
* .object()
|
||||
* .key("JSON")
|
||||
* .value("Hello, World!")
|
||||
* .endObject();</pre> which writes <pre>
|
||||
* {"JSON":"Hello, World!"}</pre>
|
||||
* <p>
|
||||
* The first method called must be <code>array</code> or <code>object</code>.
|
||||
* There are no methods for adding commas or colons. JSONWriter adds them for
|
||||
* you. Objects and arrays can be nested up to 20 levels deep.
|
||||
* <p>
|
||||
* This can sometimes be easier than using a JSONObject to build a string.
|
||||
* @author JSON.org
|
||||
* @version 2010-03-11
|
||||
*/
|
||||
public class JSONWriter {
|
||||
private static final int maxdepth = 20;
|
||||
|
||||
/**
|
||||
* The comma flag determines if a comma should be output before the next
|
||||
* value.
|
||||
*/
|
||||
private boolean comma;
|
||||
|
||||
/**
|
||||
* The current mode. Values:
|
||||
* 'a' (array),
|
||||
* 'd' (done),
|
||||
* 'i' (initial),
|
||||
* 'k' (key),
|
||||
* 'o' (object).
|
||||
*/
|
||||
protected char mode;
|
||||
|
||||
/**
|
||||
* The object/array stack.
|
||||
*/
|
||||
private JSONObject stack[];
|
||||
|
||||
/**
|
||||
* The stack top index. A value of 0 indicates that the stack is empty.
|
||||
*/
|
||||
private int top;
|
||||
|
||||
/**
|
||||
* The writer that will receive the output.
|
||||
*/
|
||||
protected Writer writer;
|
||||
|
||||
/**
|
||||
* Make a fresh JSONWriter. It can be used to build one JSON text.
|
||||
*/
|
||||
public JSONWriter(Writer w) {
|
||||
this.comma = false;
|
||||
this.mode = 'i';
|
||||
this.stack = new JSONObject[maxdepth];
|
||||
this.top = 0;
|
||||
this.writer = w;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a value.
|
||||
* @param s A string value.
|
||||
* @return this
|
||||
* @throws JSONException If the value is out of sequence.
|
||||
*/
|
||||
private JSONWriter append(String s) throws JSONException {
|
||||
if (s == null) {
|
||||
throw new JSONException("Null pointer");
|
||||
}
|
||||
if (this.mode == 'o' || this.mode == 'a') {
|
||||
try {
|
||||
if (this.comma && this.mode == 'a') {
|
||||
this.writer.write(',');
|
||||
}
|
||||
this.writer.write(s);
|
||||
} catch (IOException e) {
|
||||
throw new JSONException(e);
|
||||
}
|
||||
if (this.mode == 'o') {
|
||||
this.mode = 'k';
|
||||
}
|
||||
this.comma = true;
|
||||
return this;
|
||||
}
|
||||
throw new JSONException("Value out of sequence.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin appending a new array. All values until the balancing
|
||||
* <code>endArray</code> will be appended to this array. The
|
||||
* <code>endArray</code> method must be called to mark the array's end.
|
||||
* @return this
|
||||
* @throws JSONException If the nesting is too deep, or if the object is
|
||||
* started in the wrong place (for example as a key or after the end of the
|
||||
* outermost array or object).
|
||||
*/
|
||||
public JSONWriter array() throws JSONException {
|
||||
if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
|
||||
this.push(null);
|
||||
this.append("[");
|
||||
this.comma = false;
|
||||
return this;
|
||||
}
|
||||
throw new JSONException("Misplaced array.");
|
||||
}
|
||||
|
||||
/**
|
||||
* End something.
|
||||
* @param m Mode
|
||||
* @param c Closing character
|
||||
* @return this
|
||||
* @throws JSONException If unbalanced.
|
||||
*/
|
||||
private JSONWriter end(char m, char c) throws JSONException {
|
||||
if (this.mode != m) {
|
||||
throw new JSONException(m == 'a' ? "Misplaced endArray." :
|
||||
"Misplaced endObject.");
|
||||
}
|
||||
this.pop(m);
|
||||
try {
|
||||
this.writer.write(c);
|
||||
} catch (IOException e) {
|
||||
throw new JSONException(e);
|
||||
}
|
||||
this.comma = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* End an array. This method most be called to balance calls to
|
||||
* <code>array</code>.
|
||||
* @return this
|
||||
* @throws JSONException If incorrectly nested.
|
||||
*/
|
||||
public JSONWriter endArray() throws JSONException {
|
||||
return this.end('a', ']');
|
||||
}
|
||||
|
||||
/**
|
||||
* End an object. This method most be called to balance calls to
|
||||
* <code>object</code>.
|
||||
* @return this
|
||||
* @throws JSONException If incorrectly nested.
|
||||
*/
|
||||
public JSONWriter endObject() throws JSONException {
|
||||
return this.end('k', '}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a key. The key will be associated with the next value. In an
|
||||
* object, every value must be preceded by a key.
|
||||
* @param s A key string.
|
||||
* @return this
|
||||
* @throws JSONException If the key is out of place. For example, keys
|
||||
* do not belong in arrays or if the key is null.
|
||||
*/
|
||||
public JSONWriter key(String s) throws JSONException {
|
||||
if (s == null) {
|
||||
throw new JSONException("Null key.");
|
||||
}
|
||||
if (this.mode == 'k') {
|
||||
try {
|
||||
stack[top - 1].putOnce(s, Boolean.TRUE);
|
||||
if (this.comma) {
|
||||
this.writer.write(',');
|
||||
}
|
||||
this.writer.write(JSONObject.quote(s));
|
||||
this.writer.write(':');
|
||||
this.comma = false;
|
||||
this.mode = 'o';
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new JSONException(e);
|
||||
}
|
||||
}
|
||||
throw new JSONException("Misplaced key.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begin appending a new object. All keys and values until the balancing
|
||||
* <code>endObject</code> will be appended to this object. The
|
||||
* <code>endObject</code> method must be called to mark the object's end.
|
||||
* @return this
|
||||
* @throws JSONException If the nesting is too deep, or if the object is
|
||||
* started in the wrong place (for example as a key or after the end of the
|
||||
* outermost array or object).
|
||||
*/
|
||||
public JSONWriter object() throws JSONException {
|
||||
if (this.mode == 'i') {
|
||||
this.mode = 'o';
|
||||
}
|
||||
if (this.mode == 'o' || this.mode == 'a') {
|
||||
this.append("{");
|
||||
this.push(new JSONObject());
|
||||
this.comma = false;
|
||||
return this;
|
||||
}
|
||||
throw new JSONException("Misplaced object.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pop an array or object scope.
|
||||
* @param c The scope to close.
|
||||
* @throws JSONException If nesting is wrong.
|
||||
*/
|
||||
private void pop(char c) throws JSONException {
|
||||
if (this.top <= 0) {
|
||||
throw new JSONException("Nesting error.");
|
||||
}
|
||||
char m = this.stack[this.top - 1] == null ? 'a' : 'k';
|
||||
if (m != c) {
|
||||
throw new JSONException("Nesting error.");
|
||||
}
|
||||
this.top -= 1;
|
||||
this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1] == null ? 'a' : 'k';
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an array or object scope.
|
||||
* @param c The scope to open.
|
||||
* @throws JSONException If nesting is too deep.
|
||||
*/
|
||||
private void push(JSONObject jo) throws JSONException {
|
||||
if (this.top >= maxdepth) {
|
||||
throw new JSONException("Nesting too deep.");
|
||||
}
|
||||
this.stack[this.top] = jo;
|
||||
this.mode = jo == null ? 'a' : 'k';
|
||||
this.top += 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append either the value <code>true</code> or the value
|
||||
* <code>false</code>.
|
||||
* @param b A boolean.
|
||||
* @return this
|
||||
* @throws JSONException
|
||||
*/
|
||||
public JSONWriter value(boolean b) throws JSONException {
|
||||
return this.append(b ? "true" : "false");
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a double value.
|
||||
* @param d A double.
|
||||
* @return this
|
||||
* @throws JSONException If the number is not finite.
|
||||
*/
|
||||
public JSONWriter value(double d) throws JSONException {
|
||||
return this.value(new Double(d));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a long value.
|
||||
* @param l A long.
|
||||
* @return this
|
||||
* @throws JSONException
|
||||
*/
|
||||
public JSONWriter value(long l) throws JSONException {
|
||||
return this.append(Long.toString(l));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append an object value.
|
||||
* @param o The object to append. It can be null, or a Boolean, Number,
|
||||
* String, JSONObject, or JSONArray, or an object with a toJSONString()
|
||||
* method.
|
||||
* @return this
|
||||
* @throws JSONException If the value is out of sequence.
|
||||
*/
|
||||
public JSONWriter value(Object o) throws JSONException {
|
||||
return this.append(JSONObject.valueToString(o));
|
||||
}
|
||||
}
|
441
src/main/java/org/json/XML.java
Normal file
441
src/main/java/org/json/XML.java
Normal file
@ -0,0 +1,441 @@
|
||||
package org.json;
|
||||
|
||||
/*
|
||||
Copyright (c) 2002 JSON.org
|
||||
|
||||
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 shall be used for Good, not Evil.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
/**
|
||||
* This provides static methods to convert an XML text into a JSONObject,
|
||||
* and to covert a JSONObject into an XML text.
|
||||
* @author JSON.org
|
||||
* @version 2010-04-08
|
||||
*/
|
||||
public class XML {
|
||||
|
||||
/** The Character '&'. */
|
||||
public static final Character AMP = new Character('&');
|
||||
|
||||
/** The Character '''. */
|
||||
public static final Character APOS = new Character('\'');
|
||||
|
||||
/** The Character '!'. */
|
||||
public static final Character BANG = new Character('!');
|
||||
|
||||
/** The Character '='. */
|
||||
public static final Character EQ = new Character('=');
|
||||
|
||||
/** The Character '>'. */
|
||||
public static final Character GT = new Character('>');
|
||||
|
||||
/** The Character '<'. */
|
||||
public static final Character LT = new Character('<');
|
||||
|
||||
/** The Character '?'. */
|
||||
public static final Character QUEST = new Character('?');
|
||||
|
||||
/** The Character '"'. */
|
||||
public static final Character QUOT = new Character('"');
|
||||
|
||||
/** The Character '/'. */
|
||||
public static final Character SLASH = new Character('/');
|
||||
|
||||
/**
|
||||
* Replace special characters with XML escapes:
|
||||
* <pre>
|
||||
* & <small>(ampersand)</small> is replaced by &amp;
|
||||
* < <small>(less than)</small> is replaced by &lt;
|
||||
* > <small>(greater than)</small> is replaced by &gt;
|
||||
* " <small>(double quote)</small> is replaced by &quot;
|
||||
* </pre>
|
||||
* @param string The string to be escaped.
|
||||
* @return The escaped string.
|
||||
*/
|
||||
public static String escape(String string) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0, len = string.length(); i < len; i++) {
|
||||
char c = string.charAt(i);
|
||||
switch (c) {
|
||||
case '&':
|
||||
sb.append("&");
|
||||
break;
|
||||
case '<':
|
||||
sb.append("<");
|
||||
break;
|
||||
case '>':
|
||||
sb.append(">");
|
||||
break;
|
||||
case '"':
|
||||
sb.append(""");
|
||||
break;
|
||||
default:
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception if the string contains whitespace.
|
||||
* Whitespace is not allowed in tagNames and attributes.
|
||||
* @param string
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static void noSpace(String string) throws JSONException {
|
||||
int i, length = string.length();
|
||||
if (length == 0) {
|
||||
throw new JSONException("Empty string.");
|
||||
}
|
||||
for (i = 0; i < length; i += 1) {
|
||||
if (Character.isWhitespace(string.charAt(i))) {
|
||||
throw new JSONException("'" + string +
|
||||
"' contains a space character.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the content following the named tag, attaching it to the context.
|
||||
* @param x The XMLTokener containing the source string.
|
||||
* @param context The JSONObject that will include the new material.
|
||||
* @param name The tag name.
|
||||
* @return true if the close tag is processed.
|
||||
* @throws JSONException
|
||||
*/
|
||||
private static boolean parse(XMLTokener x, JSONObject context,
|
||||
String name) throws JSONException {
|
||||
char c;
|
||||
int i;
|
||||
String n;
|
||||
JSONObject o = null;
|
||||
String s;
|
||||
Object t;
|
||||
|
||||
// Test for and skip past these forms:
|
||||
// <!-- ... -->
|
||||
// <! ... >
|
||||
// <![ ... ]]>
|
||||
// <? ... ?>
|
||||
// Report errors for these forms:
|
||||
// <>
|
||||
// <=
|
||||
// <<
|
||||
|
||||
t = x.nextToken();
|
||||
|
||||
// <!
|
||||
|
||||
if (t == BANG) {
|
||||
c = x.next();
|
||||
if (c == '-') {
|
||||
if (x.next() == '-') {
|
||||
x.skipPast("-->");
|
||||
return false;
|
||||
}
|
||||
x.back();
|
||||
} else if (c == '[') {
|
||||
t = x.nextToken();
|
||||
if (t.equals("CDATA")) {
|
||||
if (x.next() == '[') {
|
||||
s = x.nextCDATA();
|
||||
if (s.length() > 0) {
|
||||
context.accumulate("content", s);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
throw x.syntaxError("Expected 'CDATA['");
|
||||
}
|
||||
i = 1;
|
||||
do {
|
||||
t = x.nextMeta();
|
||||
if (t == null) {
|
||||
throw x.syntaxError("Missing '>' after '<!'.");
|
||||
} else if (t == LT) {
|
||||
i += 1;
|
||||
} else if (t == GT) {
|
||||
i -= 1;
|
||||
}
|
||||
} while (i > 0);
|
||||
return false;
|
||||
} else if (t == QUEST) {
|
||||
|
||||
// <?
|
||||
|
||||
x.skipPast("?>");
|
||||
return false;
|
||||
} else if (t == SLASH) {
|
||||
|
||||
// Close tag </
|
||||
|
||||
t = x.nextToken();
|
||||
if (name == null) {
|
||||
throw x.syntaxError("Mismatched close tag" + t);
|
||||
}
|
||||
if (!t.equals(name)) {
|
||||
throw x.syntaxError("Mismatched " + name + " and " + t);
|
||||
}
|
||||
if (x.nextToken() != GT) {
|
||||
throw x.syntaxError("Misshaped close tag");
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if (t instanceof Character) {
|
||||
throw x.syntaxError("Misshaped tag");
|
||||
|
||||
// Open tag <
|
||||
|
||||
} else {
|
||||
n = (String)t;
|
||||
t = null;
|
||||
o = new JSONObject();
|
||||
for (;;) {
|
||||
if (t == null) {
|
||||
t = x.nextToken();
|
||||
}
|
||||
|
||||
// attribute = value
|
||||
|
||||
if (t instanceof String) {
|
||||
s = (String)t;
|
||||
t = x.nextToken();
|
||||
if (t == EQ) {
|
||||
t = x.nextToken();
|
||||
if (!(t instanceof String)) {
|
||||
throw x.syntaxError("Missing value");
|
||||
}
|
||||
o.accumulate(s, JSONObject.stringToValue((String)t));
|
||||
t = null;
|
||||
} else {
|
||||
o.accumulate(s, "");
|
||||
}
|
||||
|
||||
// Empty tag <.../>
|
||||
|
||||
} else if (t == SLASH) {
|
||||
if (x.nextToken() != GT) {
|
||||
throw x.syntaxError("Misshaped tag");
|
||||
}
|
||||
if (o.length() > 0) {
|
||||
context.accumulate(n, o);
|
||||
} else {
|
||||
context.accumulate(n, "");
|
||||
}
|
||||
return false;
|
||||
|
||||
// Content, between <...> and </...>
|
||||
|
||||
} else if (t == GT) {
|
||||
for (;;) {
|
||||
t = x.nextContent();
|
||||
if (t == null) {
|
||||
if (n != null) {
|
||||
throw x.syntaxError("Unclosed tag " + n);
|
||||
}
|
||||
return false;
|
||||
} else if (t instanceof String) {
|
||||
s = (String)t;
|
||||
if (s.length() > 0) {
|
||||
o.accumulate("content", JSONObject.stringToValue(s));
|
||||
}
|
||||
|
||||
// Nested element
|
||||
|
||||
} else if (t == LT) {
|
||||
if (parse(x, o, n)) {
|
||||
if (o.length() == 0) {
|
||||
context.accumulate(n, "");
|
||||
} else if (o.length() == 1 &&
|
||||
o.opt("content") != null) {
|
||||
context.accumulate(n, o.opt("content"));
|
||||
} else {
|
||||
context.accumulate(n, o);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw x.syntaxError("Misshaped tag");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a well-formed (but not necessarily valid) XML string into a
|
||||
* JSONObject. Some information may be lost in this transformation
|
||||
* because JSON is a data format and XML is a document format. XML uses
|
||||
* elements, attributes, and content text, while JSON uses unordered
|
||||
* collections of name/value pairs and arrays of values. JSON does not
|
||||
* does not like to distinguish between elements and attributes.
|
||||
* Sequences of similar elements are represented as JSONArrays. Content
|
||||
* text may be placed in a "content" member. Comments, prologs, DTDs, and
|
||||
* <code><[ [ ]]></code> are ignored.
|
||||
* @param string The source string.
|
||||
* @return A JSONObject containing the structured data from the XML string.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONObject toJSONObject(String string) throws JSONException {
|
||||
JSONObject o = new JSONObject();
|
||||
XMLTokener x = new XMLTokener(string);
|
||||
while (x.more() && x.skipPast("<")) {
|
||||
parse(x, o, null);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a JSONObject into a well-formed, element-normal XML string.
|
||||
* @param o A JSONObject.
|
||||
* @return A string.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static String toString(Object o) throws JSONException {
|
||||
return toString(o, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a JSONObject into a well-formed, element-normal XML string.
|
||||
* @param o A JSONObject.
|
||||
* @param tagName The optional name of the enclosing tag.
|
||||
* @return A string.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static String toString(Object o, String tagName)
|
||||
throws JSONException {
|
||||
StringBuffer b = new StringBuffer();
|
||||
int i;
|
||||
JSONArray ja;
|
||||
JSONObject jo;
|
||||
String k;
|
||||
Iterator keys;
|
||||
int len;
|
||||
String s;
|
||||
Object v;
|
||||
if (o instanceof JSONObject) {
|
||||
|
||||
// Emit <tagName>
|
||||
|
||||
if (tagName != null) {
|
||||
b.append('<');
|
||||
b.append(tagName);
|
||||
b.append('>');
|
||||
}
|
||||
|
||||
// Loop thru the keys.
|
||||
|
||||
jo = (JSONObject)o;
|
||||
keys = jo.keys();
|
||||
while (keys.hasNext()) {
|
||||
k = keys.next().toString();
|
||||
v = jo.opt(k);
|
||||
if (v == null) {
|
||||
v = "";
|
||||
}
|
||||
if (v instanceof String) {
|
||||
s = (String)v;
|
||||
} else {
|
||||
s = null;
|
||||
}
|
||||
|
||||
// Emit content in body
|
||||
|
||||
if (k.equals("content")) {
|
||||
if (v instanceof JSONArray) {
|
||||
ja = (JSONArray)v;
|
||||
len = ja.length();
|
||||
for (i = 0; i < len; i += 1) {
|
||||
if (i > 0) {
|
||||
b.append('\n');
|
||||
}
|
||||
b.append(escape(ja.get(i).toString()));
|
||||
}
|
||||
} else {
|
||||
b.append(escape(v.toString()));
|
||||
}
|
||||
|
||||
// Emit an array of similar keys
|
||||
|
||||
} else if (v instanceof JSONArray) {
|
||||
ja = (JSONArray)v;
|
||||
len = ja.length();
|
||||
for (i = 0; i < len; i += 1) {
|
||||
v = ja.get(i);
|
||||
if (v instanceof JSONArray) {
|
||||
b.append('<');
|
||||
b.append(k);
|
||||
b.append('>');
|
||||
b.append(toString(v));
|
||||
b.append("</");
|
||||
b.append(k);
|
||||
b.append('>');
|
||||
} else {
|
||||
b.append(toString(v, k));
|
||||
}
|
||||
}
|
||||
} else if (v.equals("")) {
|
||||
b.append('<');
|
||||
b.append(k);
|
||||
b.append("/>");
|
||||
|
||||
// Emit a new tag <k>
|
||||
|
||||
} else {
|
||||
b.append(toString(v, k));
|
||||
}
|
||||
}
|
||||
if (tagName != null) {
|
||||
|
||||
// Emit the </tagname> close tag
|
||||
|
||||
b.append("</");
|
||||
b.append(tagName);
|
||||
b.append('>');
|
||||
}
|
||||
return b.toString();
|
||||
|
||||
// XML does not have good support for arrays. If an array appears in a place
|
||||
// where XML is lacking, synthesize an <array> element.
|
||||
|
||||
} else if (o instanceof JSONArray) {
|
||||
ja = (JSONArray)o;
|
||||
len = ja.length();
|
||||
for (i = 0; i < len; ++i) {
|
||||
v = ja.opt(i);
|
||||
b.append(toString(v, (tagName == null) ? "array" : tagName));
|
||||
}
|
||||
return b.toString();
|
||||
} else {
|
||||
s = (o == null) ? "null" : escape(o.toString());
|
||||
return (tagName == null) ? "\"" + s + "\"" :
|
||||
(s.length() == 0) ? "<" + tagName + "/>" :
|
||||
"<" + tagName + ">" + s + "</" + tagName + ">";
|
||||
}
|
||||
}
|
||||
}
|
365
src/main/java/org/json/XMLTokener.java
Normal file
365
src/main/java/org/json/XMLTokener.java
Normal file
@ -0,0 +1,365 @@
|
||||
package org.json;
|
||||
|
||||
/*
|
||||
Copyright (c) 2002 JSON.org
|
||||
|
||||
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 shall be used for Good, not Evil.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The XMLTokener extends the JSONTokener to provide additional methods
|
||||
* for the parsing of XML texts.
|
||||
* @author JSON.org
|
||||
* @version 2010-01-30
|
||||
*/
|
||||
public class XMLTokener extends JSONTokener {
|
||||
|
||||
|
||||
/** The table of entity values. It initially contains Character values for
|
||||
* amp, apos, gt, lt, quot.
|
||||
*/
|
||||
public static final java.util.HashMap entity;
|
||||
|
||||
static {
|
||||
entity = new java.util.HashMap(8);
|
||||
entity.put("amp", XML.AMP);
|
||||
entity.put("apos", XML.APOS);
|
||||
entity.put("gt", XML.GT);
|
||||
entity.put("lt", XML.LT);
|
||||
entity.put("quot", XML.QUOT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an XMLTokener from a string.
|
||||
* @param s A source string.
|
||||
*/
|
||||
public XMLTokener(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text in the CDATA block.
|
||||
* @return The string up to the <code>]]></code>.
|
||||
* @throws JSONException If the <code>]]></code> is not found.
|
||||
*/
|
||||
public String nextCDATA() throws JSONException {
|
||||
char c;
|
||||
int i;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (;;) {
|
||||
c = next();
|
||||
if (end()) {
|
||||
throw syntaxError("Unclosed CDATA");
|
||||
}
|
||||
sb.append(c);
|
||||
i = sb.length() - 3;
|
||||
if (i >= 0 && sb.charAt(i) == ']' &&
|
||||
sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') {
|
||||
sb.setLength(i);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next XML outer token, trimming whitespace. There are two kinds
|
||||
* of tokens: the '<' character which begins a markup tag, and the content
|
||||
* text between markup tags.
|
||||
*
|
||||
* @return A string, or a '<' Character, or null if there is no more
|
||||
* source text.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public Object nextContent() throws JSONException {
|
||||
char c;
|
||||
StringBuffer sb;
|
||||
do {
|
||||
c = next();
|
||||
} while (Character.isWhitespace(c));
|
||||
if (c == 0) {
|
||||
return null;
|
||||
}
|
||||
if (c == '<') {
|
||||
return XML.LT;
|
||||
}
|
||||
sb = new StringBuffer();
|
||||
for (;;) {
|
||||
if (c == '<' || c == 0) {
|
||||
back();
|
||||
return sb.toString().trim();
|
||||
}
|
||||
if (c == '&') {
|
||||
sb.append(nextEntity(c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
c = next();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the next entity. These entities are translated to Characters:
|
||||
* <code>& ' > < "</code>.
|
||||
* @param a An ampersand character.
|
||||
* @return A Character or an entity String if the entity is not recognized.
|
||||
* @throws JSONException If missing ';' in XML entity.
|
||||
*/
|
||||
public Object nextEntity(char a) throws JSONException {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (;;) {
|
||||
char c = next();
|
||||
if (Character.isLetterOrDigit(c) || c == '#') {
|
||||
sb.append(Character.toLowerCase(c));
|
||||
} else if (c == ';') {
|
||||
break;
|
||||
} else {
|
||||
throw syntaxError("Missing ';' in XML entity: &" + sb);
|
||||
}
|
||||
}
|
||||
String s = sb.toString();
|
||||
Object e = entity.get(s);
|
||||
return e != null ? e : a + s + ";";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next XML meta token. This is used for skipping over <!...>
|
||||
* and <?...?> structures.
|
||||
* @return Syntax characters (<code>< > / = ! ?</code>) are returned as
|
||||
* Character, and strings and names are returned as Boolean. We don't care
|
||||
* what the values actually are.
|
||||
* @throws JSONException If a string is not properly closed or if the XML
|
||||
* is badly structured.
|
||||
*/
|
||||
public Object nextMeta() throws JSONException {
|
||||
char c;
|
||||
char q;
|
||||
do {
|
||||
c = next();
|
||||
} while (Character.isWhitespace(c));
|
||||
switch (c) {
|
||||
case 0:
|
||||
throw syntaxError("Misshaped meta tag");
|
||||
case '<':
|
||||
return XML.LT;
|
||||
case '>':
|
||||
return XML.GT;
|
||||
case '/':
|
||||
return XML.SLASH;
|
||||
case '=':
|
||||
return XML.EQ;
|
||||
case '!':
|
||||
return XML.BANG;
|
||||
case '?':
|
||||
return XML.QUEST;
|
||||
case '"':
|
||||
case '\'':
|
||||
q = c;
|
||||
for (;;) {
|
||||
c = next();
|
||||
if (c == 0) {
|
||||
throw syntaxError("Unterminated string");
|
||||
}
|
||||
if (c == q) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
}
|
||||
default:
|
||||
for (;;) {
|
||||
c = next();
|
||||
if (Character.isWhitespace(c)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
switch (c) {
|
||||
case 0:
|
||||
case '<':
|
||||
case '>':
|
||||
case '/':
|
||||
case '=':
|
||||
case '!':
|
||||
case '?':
|
||||
case '"':
|
||||
case '\'':
|
||||
back();
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the next XML Token. These tokens are found inside of angle
|
||||
* brackets. It may be one of these characters: <code>/ > = ! ?</code> or it
|
||||
* may be a string wrapped in single quotes or double quotes, or it may be a
|
||||
* name.
|
||||
* @return a String or a Character.
|
||||
* @throws JSONException If the XML is not well formed.
|
||||
*/
|
||||
public Object nextToken() throws JSONException {
|
||||
char c;
|
||||
char q;
|
||||
StringBuffer sb;
|
||||
do {
|
||||
c = next();
|
||||
} while (Character.isWhitespace(c));
|
||||
switch (c) {
|
||||
case 0:
|
||||
throw syntaxError("Misshaped element");
|
||||
case '<':
|
||||
throw syntaxError("Misplaced '<'");
|
||||
case '>':
|
||||
return XML.GT;
|
||||
case '/':
|
||||
return XML.SLASH;
|
||||
case '=':
|
||||
return XML.EQ;
|
||||
case '!':
|
||||
return XML.BANG;
|
||||
case '?':
|
||||
return XML.QUEST;
|
||||
|
||||
// Quoted string
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
q = c;
|
||||
sb = new StringBuffer();
|
||||
for (;;) {
|
||||
c = next();
|
||||
if (c == 0) {
|
||||
throw syntaxError("Unterminated string");
|
||||
}
|
||||
if (c == q) {
|
||||
return sb.toString();
|
||||
}
|
||||
if (c == '&') {
|
||||
sb.append(nextEntity(c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
||||
// Name
|
||||
|
||||
sb = new StringBuffer();
|
||||
for (;;) {
|
||||
sb.append(c);
|
||||
c = next();
|
||||
if (Character.isWhitespace(c)) {
|
||||
return sb.toString();
|
||||
}
|
||||
switch (c) {
|
||||
case 0:
|
||||
return sb.toString();
|
||||
case '>':
|
||||
case '/':
|
||||
case '=':
|
||||
case '!':
|
||||
case '?':
|
||||
case '[':
|
||||
case ']':
|
||||
back();
|
||||
return sb.toString();
|
||||
case '<':
|
||||
case '"':
|
||||
case '\'':
|
||||
throw syntaxError("Bad character in a name");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Skip characters until past the requested string.
|
||||
* If it is not found, we are left at the end of the source with a result of false.
|
||||
* @param to A string to skip past.
|
||||
* @throws JSONException
|
||||
*/
|
||||
public boolean skipPast(String to) throws JSONException {
|
||||
boolean b;
|
||||
char c;
|
||||
int i;
|
||||
int j;
|
||||
int offset = 0;
|
||||
int n = to.length();
|
||||
char[] circle = new char[n];
|
||||
|
||||
/*
|
||||
* First fill the circle buffer with as many characters as are in the
|
||||
* to string. If we reach an early end, bail.
|
||||
*/
|
||||
|
||||
for (i = 0; i < n; i += 1) {
|
||||
c = next();
|
||||
if (c == 0) {
|
||||
return false;
|
||||
}
|
||||
circle[i] = c;
|
||||
}
|
||||
/*
|
||||
* We will loop, possibly for all of the remaining characters.
|
||||
*/
|
||||
for (;;) {
|
||||
j = offset;
|
||||
b = true;
|
||||
/*
|
||||
* Compare the circle buffer with the to string.
|
||||
*/
|
||||
for (i = 0; i < n; i += 1) {
|
||||
if (circle[j] != to.charAt(i)) {
|
||||
b = false;
|
||||
break;
|
||||
}
|
||||
j += 1;
|
||||
if (j >= n) {
|
||||
j -= n;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If we exit the loop with b intact, then victory is ours.
|
||||
*/
|
||||
if (b) {
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* Get the next character. If there isn't one, then defeat is ours.
|
||||
*/
|
||||
c = next();
|
||||
if (c == 0) {
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
* Shove the character in the circle buffer and advance the
|
||||
* circle offset. The offset is mod n.
|
||||
*/
|
||||
circle[offset] = c;
|
||||
offset += 1;
|
||||
if (offset >= n) {
|
||||
offset -= n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user