Index: src/CacheWolf/PreferencesScreen.java =================================================================== --- src/CacheWolf/PreferencesScreen.java (Revision 2717) +++ src/CacheWolf/PreferencesScreen.java (Arbeitskopie) @@ -362,7 +362,7 @@ Editor s = gpo.getEditor(); gpo.forwardGpsChkB.setState(pref.forwardGPS); gpo.inputBoxForwardHost.setText(pref.forwardGpsHost); - gpo.gpsdChkB.setState(pref.useGPSD); + gpo.chcUseGpsd.select(pref.useGPSD); if(pref.gpsdPort!=pref.DEFAULT_GPSD_PORT){ gpo.inputBoxGpsdHost.setText(pref.gpsdHost + ":" + Convert.toString(pref.gpsdPort)); }else{ @@ -376,7 +376,7 @@ pref.mySPO.baudRate = gpo.baudRate; pref.forwardGPS = gpo.forwardGpsChkB.getState(); pref.forwardGpsHost = gpo.inputBoxForwardHost.getText(); - pref.useGPSD = gpo.gpsdChkB.getState(); + pref.useGPSD = gpo.chcUseGpsd.getInt(); String gpsdHostString = gpo.inputBoxGpsdHost.getText(); // hostname[:port] int posColon = gpsdHostString.indexOf(':'); if(posColon>=0){ Index: src/CacheWolf/Preferences.java =================================================================== --- src/CacheWolf/Preferences.java (Revision 2717) +++ src/CacheWolf/Preferences.java (Arbeitskopie) @@ -43,6 +43,9 @@ public final int DEFAULT_LOGS_PER_PAGE=5; public final int DEFAULT_INITIAL_HINT_HEIGHT=10; public final int DEFAULT_GPSD_PORT=2947; + public static final int GPSD_DISABLED = 0; // do not use gpsd + public static final int GPSD_FORMAT_OLD = 1; // use old protocol + public static final int GPSD_FORMAT_NEW = 2; // use new protocol (JSON) public static final int YES = 0; public static final int NO = 1; public static final int ASK = 2; @@ -167,8 +170,8 @@ public boolean forwardGPS = false; /** IP address for forwarding GPS data */ public String forwardGpsHost = "192.168.1.15"; - /** True if the GPS data should be received from a GPSD on this or another host */ - public boolean useGPSD = false; + /** Should GPS data be received from a GPSD on this or another host? */ + public int useGPSD = GPSD_DISABLED; /** IP address of GPSD host */ public String gpsdHost = "127.0.0.1"; /** Port for forwarding GPS data */ @@ -438,7 +441,7 @@ forwardGpsHost = atts.getValue("destinationHost"); } else if(name.equals("gpsd")) { - useGPSD = Convert.toBoolean(atts.getValue("active")); + useGPSD = Convert.toInt(atts.getValue("active")); gpsdHost = atts.getValue("host"); gpsdPort = Convert.toInt(atts.getValue("port")); } @@ -685,7 +688,7 @@ outp.print(" \n"); outp.print(" \n"); outp.print(" \n"); - outp.print(" \n"); + outp.print(" \n"); outp.print(" \n"); outp.print(" \n"); outp.print(" This is ugly, but BufferedReader::ready() seems to + * be broken in Ewe, so instead of only polling when + * there is no data from gpsd, we poll on every iteration. + * Not ideal, but works for now. + */ + gpsObj.poll(); + + /* Tblue> TODO: I think this call should not block, but + * my GPS class does not yet support non-blocking + * reads... Seems to work, anyway. + */ + response = gpsObj.read(); + out.appendText(response.toString(2) + "\n", true); + + // Keep up with new devices: + if( response.getString( "class" ).equals( "DEVICE" ) && + response.has( "activated" ) && response.getDouble( "activated" ) != 0 ) + { // This is a new device, we need to tell gpsd we want to watch it: + Global.getPref().log( "New GPS device, sending WATCH command." ); + gpsObj.stream( GPS.WATCH_ENABLE ); + } + } catch( Exception e ) { + // We will just ignore this JSON object: + Global.getPref().log("Ignored Exception", e, true); + } + } // while + } + + + public boolean stop() { + run = false; + + if( gpsObj == null ) { + return true; + } + + gpsObj.cleanup(); + return false; + } +} + +class OldGpsdThread extends mThread{ Socket gpsdSocket; boolean run; TextDisplay out; @@ -103,7 +203,7 @@ String lastError = new String(); public String lastgot; - public GpsdThread(TextDisplay td) throws IOException { + public OldGpsdThread(TextDisplay td) throws IOException { try{ gpsdSocket = new Socket(Global.getPref().gpsdHost, Global.getPref().gpsdPort); } catch (IOException e) { @@ -177,17 +277,25 @@ mLabel labelForwardHost; public mCheckBox forwardGpsChkB; public mInput inputBoxGpsdHost; + mLabel labelUseGpsd; + public mChoice chcUseGpsd; mLabel labelGpsdHost; - public mCheckBox gpsdChkB; public mInput inputBoxLogTimer; mLabel labelLogTimer; public mCheckBox logGpsChkB; mySerialThread serThread; GpsdThread gpsdThread = null; + OldGpsdThread oldGpsdThread = null; boolean gpsRunning = false; MyEditor ed = new MyEditor(); - + private String[] useGpsdChoices = new String[]{ + MyLocale.getMsg(641, "No"), + MyLocale.getMsg(99999, "Yes (< v2.91)"), + MyLocale.getMsg(99999, "Yes (>= v2.91)"), + }; + + public Editor getEditor(){ // The following lines are mainly copied from SerialPortOptions. // Reason: We want to use MyEditor instead of the default Editor, @@ -224,29 +332,34 @@ forwardGpsChkB = new mCheckBox(""); ed.addField(ed.addNext(forwardGpsChkB, CellConstants.DONTSTRETCH, (CellConstants.EAST | CellConstants.DONTFILL)), "forwardGpsChkB"); - labelForwardHost = new mLabel(MyLocale.getMsg(7105, "Forward GPS data to host")); + labelForwardHost = new mLabel(MyLocale.getMsg(7105, "Forward GPS data to host (serial port only)")); ed.addField(ed.addNext(labelForwardHost, CellConstants.DONTSTRETCH, (CellConstants.WEST | CellConstants.DONTFILL)), "labelForwardIP"); inputBoxForwardHost = new mInput("tcpForwardHost"); inputBoxForwardHost.setPromptControl(labelForwardHost); inputBoxForwardHost.setToolTip(MyLocale.getMsg(7106, "All data from GPS will be sent to TCP-port 23\n and can be redirected there to a serial port\n by HW Virtual Serial Port")); ed.addField(ed.addLast(inputBoxForwardHost,0 , (CellConstants.WEST | CellConstants.HFILL)), "tcpForwardHost"); - gpsdChkB = new mCheckBox(""); - ed.addField(ed.addNext(gpsdChkB, CellConstants.DONTSTRETCH, (CellConstants.EAST | CellConstants.DONTFILL)), "gpsdChkB"); - labelGpsdHost = new mLabel(MyLocale.getMsg(7121, "Receive GPS from gpsd host")); - ed.addField(ed.addNext(labelGpsdHost, CellConstants.DONTSTRETCH, (CellConstants.WEST | CellConstants.DONTFILL)), "labelGpsd"); - inputBoxGpsdHost = new mInput("GpsdHost"); - inputBoxGpsdHost.setPromptControl(labelGpsdHost); - inputBoxGpsdHost.setToolTip(MyLocale.getMsg(7122, "GPS data will be received from a gpsd server, not from a serial port")); - ed.addField(ed.addLast(inputBoxGpsdHost,0 , (CellConstants.WEST | CellConstants.HFILL)), "GpsdHost"); - logGpsChkB = new mCheckBox(""); ed.addField(ed.addNext(logGpsChkB, CellConstants.DONTSTRETCH, (CellConstants.EAST | CellConstants.DONTFILL)), "logGpsChkB"); - labelLogTimer = new mLabel(MyLocale.getMsg(7107, "Interval in sec for logging")); + labelLogTimer = new mLabel(MyLocale.getMsg(7107, "Interval in sec for logging (serial port only)")); ed.addField(ed.addNext(labelLogTimer, CellConstants.DONTSTRETCH, (CellConstants.WEST | CellConstants.DONTFILL)), "labelLogTimer"); inputBoxLogTimer = new mInput("GPSLogTimer"); inputBoxLogTimer.setPromptControl(labelLogTimer); - ed.addField(ed.addLast(inputBoxLogTimer,0 , (CellConstants.WEST | CellConstants.HFILL)), "GPSLogTimer"); + ed.addField(ed.addLast(inputBoxLogTimer, 0, (CellConstants.WEST | CellConstants.HFILL)), "GPSLogTimer"); + + labelUseGpsd = new mLabel(MyLocale.getMsg(7121, "Receive GPS data from gpsd:")); + ed.addField(ed.addNext(labelUseGpsd, CellConstants.DONTSTRETCH, (CellConstants.EAST | CellConstants.DONTFILL)), "labelUseGpsd"); + chcUseGpsd = new mChoice( useGpsdChoices, 0 ); + chcUseGpsd.setPromptControl(labelUseGpsd); + chcUseGpsd.setToolTip(MyLocale.getMsg(7122, "GPS data will be received from a gpsd server, not from a serial port")); + ed.addField(ed.addLast(chcUseGpsd, 0, (CellConstants.WEST | CellConstants.HFILL)), "UseGpsd"); + + labelGpsdHost = new mLabel(MyLocale.getMsg(99999, "gpsd host:")); + ed.addField(ed.addNext(labelGpsdHost, CellConstants.DONTSTRETCH, (CellConstants.EAST | CellConstants.DONTFILL)), "labelGpsdHost"); + inputBoxGpsdHost = new mInput("GpsdHost"); + inputBoxGpsdHost.setPromptControl(labelGpsdHost); + ed.addField(ed.addLast(inputBoxGpsdHost, 0, (CellConstants.WEST | CellConstants.HFILL)), "GpsdHost"); + this.ed.firstFocus = btnUpdatePortList; gpsRunning = false; return ed; @@ -306,37 +419,67 @@ if (field.equals("test")){ if (!gpsRunning){ ed_.fromControls(); - if(Global.getPref().useGPSD){ - txtOutput.setText(MyLocale.getMsg(99999, "Displaying data from gpsd directly:\n")); - try { - btnScan.set(ControlConstants.Disabled, true); - btnScan.repaintNow(); - gpsdThread = new GpsdThread(txtOutput); - gpsdThread.start(); - btnTest.setText(Gui.getTextFrom(MyLocale.getMsg(7118, "Stop"))); - gpsRunning = true; - } catch (IOException e) { - (new MessageBox(MyLocale.getMsg(4403, "Error"), - MyLocale.getMsg(99999, "Could not connect to GPSD.") - + e.getMessage() - + MyLocale.getMsg(99999, "\npossible reasons:\nGPSD is not running or GPSD host is not reachable"), - FormBase.OKB)).execute(); - } - }else{ - txtOutput.setText(MyLocale.getMsg(7117, "Displaying data from serial port directly:\n")); - try { - btnScan.set(ControlConstants.Disabled, true); - btnScan.repaintNow(); - this.portName = Common.fixSerialPortName(portName); - serThread = new mySerialThread(this, txtOutput); - serThread.start(); - btnTest.setText(Gui.getTextFrom(MyLocale.getMsg(7118, "Stop"))); - gpsRunning = true; - } catch (IOException e) { - btnScan.set(ControlConstants.Disabled, false); - btnScan.repaintNow(); - txtOutput.appendText(MyLocale.getMsg(7108, "Failed to open serial port: ") + this.portName + ", IOException: " + e.getMessage() + "\n", true); - } + + switch(Global.getPref().useGPSD) { + case Preferences.GPSD_FORMAT_NEW: + txtOutput.setText(MyLocale.getMsg(99999, "Displaying data from gpsd directly (JSON):\n")); + try { + btnScan.set(ControlConstants.Disabled, true); + btnScan.repaintNow(); + gpsdThread = new GpsdThread(txtOutput); + gpsdThread.start(); + btnTest.setText(Gui.getTextFrom(MyLocale.getMsg(7118, "Stop"))); + gpsRunning = true; + } catch (IOException e) { + (new MessageBox(MyLocale.getMsg(4403, "Error"), + MyLocale.getMsg(99999, "Could not connect to GPSD: ") + + e.getMessage() + + MyLocale.getMsg(99999, "\nPossible reasons:\nGPSD is not running or GPSD host is not reachable"), + FormBase.OKB)).execute(); + } catch( Exception e ) { + // Other error (JSON/GPS). + (new MessageBox(MyLocale.getMsg(4403, "Error"), + MyLocale.getMsg(99999, "Could not initialize GPSD connection: ") + + e.getMessage(), + FormBase.OKB)).execute(); + } + break; + + case Preferences.GPSD_FORMAT_OLD: + txtOutput.setText(MyLocale.getMsg(99999, "Displaying data from gpsd directly (old protocol):\n")); + try { + btnScan.set(ControlConstants.Disabled, true); + btnScan.repaintNow(); + oldGpsdThread = new OldGpsdThread(txtOutput); + oldGpsdThread.start(); + btnTest.setText(Gui.getTextFrom(MyLocale.getMsg(7118, "Stop"))); + gpsRunning = true; + } catch (IOException e) { + (new MessageBox(MyLocale.getMsg(4403, "Error"), + MyLocale.getMsg(99999, "Could not connect to GPSD: ") + + e.getMessage() + + MyLocale.getMsg(99999, "\nPossible reasons:\nGPSD is not running or GPSD host is not reachable"), + FormBase.OKB)).execute(); + } + break; + + case Preferences.GPSD_DISABLED: + default: + txtOutput.setText(MyLocale.getMsg(7117, "Displaying data from serial port directly:\n")); + try { + btnScan.set(ControlConstants.Disabled, true); + btnScan.repaintNow(); + this.portName = Common.fixSerialPortName(portName); + serThread = new mySerialThread(this, txtOutput); + serThread.start(); + btnTest.setText(Gui.getTextFrom(MyLocale.getMsg(7118, "Stop"))); + gpsRunning = true; + } catch (IOException e) { + btnScan.set(ControlConstants.Disabled, false); + btnScan.repaintNow(); + txtOutput.appendText(MyLocale.getMsg(7108, "Failed to open serial port: ") + this.portName + ", IOException: " + e.getMessage() + "\n", true); + } + break; } } else { @@ -344,6 +487,8 @@ serThread.stop(); if(gpsdThread!=null) gpsdThread.stop(); + if(oldGpsdThread!=null) + oldGpsdThread.stop(); btnTest.setText(Gui.getTextFrom(MyLocale.getMsg(7104,"Test$t"))); gpsRunning = false; btnScan.set(ControlConstants.Disabled, false); Index: src/CacheWolf/navi/CWGPSPoint.java =================================================================== --- src/CacheWolf/navi/CWGPSPoint.java (Revision 2717) +++ src/CacheWolf/navi/CWGPSPoint.java (Arbeitskopie) @@ -14,6 +14,10 @@ import ewe.sys.*; import ewe.io.*; +// Stuff needed by CWGPSPoint::examineGpsd(): +import org.json.JSONObject; +import org.json.JSONArray; +import org.json.JSONException; /** @@ -26,15 +30,15 @@ public static final int LOGRAW = 0x02; public static final int LOGALL = LOGNMEA|LOGRAW; - public double Speed; //Speed - public double Bear; //Bearing - public String Time; //Time - public String Date; - public int Fix; //Fix - public int numSat; //Satellites in use, -1 indicates no data, -2 that data could not be interpreted - public int numSatsInView; //Satellites in view + public double Speed; // Speed: km/h + public double Bear; // Bearing + public String Time; // Time: HHmmss.SS + public String Date; // Date: ddMMyy + public int Fix; // Fix (0: none, 1: GPS, 2: differential GPS). See getFix() for more possible values. + public int numSat; // Satellites in use, -1 indicates no data, -2 that data could not be interpreted + public int numSatsInView; // Satellites in view public double HDOP; // Horizontal dilution of precision - public double Alt; //Altitude + public double Alt; // Altitude in meters //Logging int logTimer = 0; @@ -384,12 +388,95 @@ /** - * Sets the attributes from a GPSD string + * Sets the attributes from a GPSD POLL object + * + * @param gps {@link JSONObject} containing GPS POLL data. + * @return true if some data could be interpreted false otherwise + * Tblue> For now, this always returns true. Any ideas what + * should be treated as not interpretable? + * @throws JSONException When trying to access a not existing key (should not happen!). + */ + public boolean examineGpsd(JSONObject gps) throws JSONException { + JSONArray fixes = gps.getJSONArray( "fixes" ); + JSONArray skyviews = gps.getJSONArray( "skyviews" ); + JSONArray sats; + JSONObject a_fix, a_skyview; + int fix_mode, i; + double my_lat, my_lon; + Time TimeObj = new Time(); + + lastStrExamined = gps.toString(); + + TimeObj.setTime( (long)( gps.getDouble( "timestamp" ) * 1000 ) ); + this.Time = TimeObj.format( "HHmmss.SS" ); + this.Date = TimeObj.format( "ddMMyy" ); + + if( fixes.length() > 0 ) { + // We will only use the first fix. + // TODO: Randomize? + a_fix = fixes.getJSONObject( 0 ); + + // 0: no mode seen yet, 1: none, 2: 2D, 3: 3D. + // Tblue> Does 3D mean differential here? + this.Fix = ( fix_mode = a_fix.getInt( "mode" ) ) > 0 ? fix_mode - 1 : 0; + + // Speed is in m/s. + if( a_fix.has( "speed" ) ) { + this.Speed = ( a_fix.getDouble( "speed" ) / 1000 ) * 60 * 60; + } + + if( a_fix.has( "track" ) ) { + this.Bear = a_fix.getDouble( "track" ); + } + + if( a_fix.has( "alt" ) ) { + this.Alt = a_fix.getDouble( "alt" ); + } + + if( a_fix.has( "lat" ) && a_fix.has( "lon" ) ) { + my_lat = a_fix.getDouble( "lat" ); + my_lon = a_fix.getDouble( "lon" ); + + set( my_lat > 0 ? "N" : "S", String.valueOf( my_lat ), "0", "0", + my_lon > 0 ? "E" : "W", String.valueOf( my_lon ), "0", "0", + TransformCoordinates.DD ); + } + } + + if( skyviews.length() > 0 ) + { + // We will only use the first skyview. + // TODO: Randomize? + a_skyview = skyviews.getJSONObject( 0 ); + + if( a_skyview.has( "hdop" ) ) { + this.HDOP = a_skyview.getDouble( "hdop" ); + } + + sats = a_skyview.getJSONArray( "satellites" ); + this.numSatsInView = sats.length(); + + if( this.numSatsInView > 0 ) + { + for( this.numSat = 0, i = 0; i < this.numSatsInView; i++ ) { + if( sats.getJSONObject( i ).getBoolean( "used" ) ) { + this.numSat++; + } + } + } + } + + return true; + } + + + /** + * Sets the attributes from an old-style GPSD string. * @param gps GPSD string with data to examine * Format: GPSD,key=value,... * @return true if some data could be interpreted false otherwise */ - public boolean examineGpsd(String gps){ + public boolean examineOldGpsd(String gps){ boolean valid = false; if(!gps.startsWith("GPSD,")) return false; @@ -455,6 +542,7 @@ return valid; } + private boolean checkSumOK(String nmea){ int startPos = 1; // begin after $ int endPos = nmea.length() - 3;// without * an two checksum chars Index: src/CacheWolf/navi/GotoPanel.java =================================================================== --- src/CacheWolf/navi/GotoPanel.java (Revision 2717) +++ src/CacheWolf/navi/GotoPanel.java (Arbeitskopie) @@ -264,13 +264,13 @@ } // receiving no data if (fix == -1) { - if (gpsStatus != RED) (new MessageBox(MyLocale.getMsg(321, "Error"), MyLocale.getMsg(1510, "No data from GPS\nConnection to serial port closed"),FormBase.OKB)).exec(); + if (gpsStatus != RED) (new MessageBox(MyLocale.getMsg(321, "Error"), MyLocale.getMsg(1510, "No data from GPS.\nConnection to serial port/gpsd closed."),FormBase.OKB)).exec(); gpsStatus = RED; myNavigation.stopGps(); } // cannot interpret data if (fix == -2) { - if (gpsStatus != RED) (new MessageBox(MyLocale.getMsg(321, "Error"), MyLocale.getMsg(1511, "Cannot interpret data from GPS\n possible reasons:\n wrong Port,\n wrong Baudrate,\n not NMEA-Protocol\nConnection to serial port closed\nLast String tried to interprete:\n")+myNavigation.gpsPos.lastStrExamined, FormBase.OKB)).exec(); + if (gpsStatus != RED) (new MessageBox(MyLocale.getMsg(321, "Error"), MyLocale.getMsg(1511, "Cannot interpret data from GPS/gpsd!\nPossible reasons:\nWrong port,\nwrong baud rate,\ninvalid protocol (need NMEA/gpsd).\nConnection to serial port closed.\nLast String tried to interpret:\n")+myNavigation.gpsPos.lastStrExamined, FormBase.OKB)).exec(); gpsStatus = RED; myNavigation.stopGps(); // TODO automatic in myNavigate? } Index: src/CacheWolf/navi/Navigate.java =================================================================== --- src/CacheWolf/navi/Navigate.java (Revision 2717) +++ src/CacheWolf/navi/Navigate.java (Arbeitskopie) @@ -10,13 +10,20 @@ import ewe.io.SerialPortOptions; import ewe.net.Socket; import ewe.sys.mThread; +import ewe.sys.Vm; import ewe.ui.FormBase; import ewe.ui.MessageBox; import ewe.util.mString; import CacheWolf.CacheHolder; import CacheWolf.navi.MovingMap; +// Stuff needed by the GpsdThread class: +import net.ax86.GPS; +import net.ax86.GPSException; +import org.json.JSONObject; +import org.json.JSONException; + /** * Non-Gui Class to handle all things regarding navigation * (GPS, Sun direction etc.) @@ -37,6 +44,7 @@ public GotoPanel gotoPanel = null; public MovingMap movingMap = null; public GpsdThread gpsdThread = null; + public OldGpsdThread oldGpsdThread = null; public SerialThread serThread = null; public Preferences pref = Global.getPref(); public UpdateThread tickerThread; @@ -55,52 +63,84 @@ public void startGps(boolean loggingOn, int loggingIntervall) { lograw = loggingOn; logIntervall = loggingIntervall; // TODO switch on and off during serthread running - if(Global.getPref().useGPSD){ - try { - gpsdThread = new GpsdThread(gpsPos); - gpsdThread.start(); - startDisplayTimer(); - gpsRunning = true; - curTrack = new Track(trackColor); // TODO addTrack here to MovingMap? see MovingMapPanel.snapToGps - if (lograw) gpsPos.startLog(Global.getProfile().dataDir, logIntervall, CWGPSPoint.LOGALL); - if (gotoPanel != null) gotoPanel.gpsStarted(); - if (movingMap != null) movingMap.gpsStarted(); - } catch (IOException e) { - (new MessageBox(MyLocale.getMsg(4403, "Error"), - MyLocale.getMsg(4408, "Could not connect to GPSD:") - + e.getMessage() - + MyLocale.getMsg(4409, "\npossible reasons:\nGPSD is not running or GPSD host is not reachable"), - FormBase.OKB)).execute(); - } - }else{ - if (serThread != null) if (serThread.isAlive()) return; // TODO use gpsRunning - try { - serThread = new SerialThread(pref.mySPO, gpsPos, (pref.forwardGPS ? pref.forwardGpsHost : "")); - if (pref.forwardGPS && !serThread.tcpForward) { - (new MessageBox(MyLocale.getMsg(4400, "Warning"), - MyLocale.getMsg(4401, "Ignoring error:\n could not forward GPS data to host:\n") - + pref.forwardGpsHost+"\n" + serThread.lastError - + MyLocale.getMsg(4402, "\nstop and start GPS to retry"), FormBase.OKB)).exec(); + + switch(Global.getPref().useGPSD) { + // Tblue> TODO: NEW vs. OLD: This is ugly! The only line that's + // different is the one where the object is created! + case Preferences.GPSD_FORMAT_NEW: + try { + gpsdThread = new GpsdThread(gpsPos); + gpsdThread.start(); + startDisplayTimer(); + gpsRunning = true; + curTrack = new Track(trackColor); // TODO addTrack here to MovingMap? see MovingMapPanel.snapToGps + if (gotoPanel != null) gotoPanel.gpsStarted(); + if (movingMap != null) movingMap.gpsStarted(); + } catch (IOException e) { + (new MessageBox(MyLocale.getMsg(4403, "Error"), + MyLocale.getMsg(4408, "Could not connect to GPSD: ") + + e.getMessage() + + MyLocale.getMsg(4409, "\nPossible reasons:\nGPSD is not running or GPSD host is not reachable"), + FormBase.OKB)).execute(); + } catch( Exception e ) { + // Other error (JSON/GPS). + (new MessageBox(MyLocale.getMsg(4403, "Error"), + MyLocale.getMsg(99999, "Could not initialize GPSD connection: ") + + e.getMessage(), + FormBase.OKB)).execute(); } - serThread.start(); - startDisplayTimer(); - gpsRunning = true; - curTrack = new Track(trackColor); // TODO addTrack here to MovingMap? see MovingMapPanel.snapToGps - if (lograw) gpsPos.startLog(Global.getProfile().dataDir, logIntervall, CWGPSPoint.LOGALL); - if (gotoPanel != null) gotoPanel.gpsStarted(); - if (movingMap != null) movingMap.gpsStarted(); - } catch (IOException e) { - (new MessageBox(MyLocale.getMsg(4403, "Error"), - MyLocale.getMsg(4404, "Could not connect to GPS-receiver.\n Error while opening serial Port ") + break; + + case Preferences.GPSD_FORMAT_OLD: + try { + oldGpsdThread = new OldGpsdThread(gpsPos); + oldGpsdThread.start(); + startDisplayTimer(); + gpsRunning = true; + curTrack = new Track(trackColor); // TODO addTrack here to MovingMap? see MovingMapPanel.snapToGps + if (lograw) gpsPos.startLog(Global.getProfile().dataDir, logIntervall, CWGPSPoint.LOGALL); + if (gotoPanel != null) gotoPanel.gpsStarted(); + if (movingMap != null) movingMap.gpsStarted(); + } catch (IOException e) { + (new MessageBox(MyLocale.getMsg(4403, "Error"), + MyLocale.getMsg(4408, "Could not connect to GPSD: ") + e.getMessage() - + MyLocale.getMsg(4405, "\npossible reasons:\n Another (GPS-)program is blocking the port\nwrong port\nOn Loox: active infra-red port is blocking GPS"), + + MyLocale.getMsg(4409, "\nPossible reasons:\nGPSD is not running or GPSD host is not reachable"), FormBase.OKB)).execute(); - } catch (UnsatisfiedLinkError e) { - (new MessageBox(MyLocale.getMsg(4403, "Error"), - MyLocale.getMsg(4404, "Could not connect to GPS-receiver.\n Error while opening serial Port ") - + MyLocale.getMsg(4406, "Please copy jave_ewe.dll into the directory of the cachewolf program"), - FormBase.OKB)).execute(); - } + } + break; + + case Preferences.GPSD_DISABLED: + default: + if (serThread != null) if (serThread.isAlive()) return; // TODO use gpsRunning + try { + serThread = new SerialThread(pref.mySPO, gpsPos, (pref.forwardGPS ? pref.forwardGpsHost : "")); + if (pref.forwardGPS && !serThread.tcpForward) { + (new MessageBox(MyLocale.getMsg(4400, "Warning"), + MyLocale.getMsg(4401, "Ignoring error:\n could not forward GPS data to host:\n") + + pref.forwardGpsHost+"\n" + serThread.lastError + + MyLocale.getMsg(4402, "\nstop and start GPS to retry"), FormBase.OKB)).exec(); + } + serThread.start(); + startDisplayTimer(); + gpsRunning = true; + curTrack = new Track(trackColor); // TODO addTrack here to MovingMap? see MovingMapPanel.snapToGps + if (lograw) gpsPos.startLog(Global.getProfile().dataDir, logIntervall, CWGPSPoint.LOGALL); + if (gotoPanel != null) gotoPanel.gpsStarted(); + if (movingMap != null) movingMap.gpsStarted(); + } catch (IOException e) { + (new MessageBox(MyLocale.getMsg(4403, "Error"), + MyLocale.getMsg(4404, "Could not connect to GPS-receiver.\n Error while opening serial Port ") + + e.getMessage() + + MyLocale.getMsg(4405, "\npossible reasons:\n Another (GPS-)program is blocking the port\nwrong port\nOn Loox: active infra-red port is blocking GPS"), + FormBase.OKB)).execute(); + } catch (UnsatisfiedLinkError e) { + (new MessageBox(MyLocale.getMsg(4403, "Error"), + MyLocale.getMsg(4404, "Could not connect to GPS-receiver.\n Error while opening serial Port ") + + MyLocale.getMsg(4406, "Please copy jave_ewe.dll into the directory of the cachewolf program"), + FormBase.OKB)).execute(); + } + break; } } public void startDisplayTimer() { @@ -115,6 +155,7 @@ public void stopGps() { if(serThread!=null) serThread.stop(); if(gpsdThread!=null) gpsdThread.stop(); + if(oldGpsdThread!=null) oldGpsdThread.stop(); stopDisplayTimer(); gpsPos.stopLog(); gpsRunning = false; @@ -123,7 +164,10 @@ } public boolean isGpsPosValid() { - return (serThread != null && serThread.isAlive() || gpsdThread != null && gpsdThread.isAlive() ) && gpsPos.isValid() ; // && gpsPos.getfiex(); + return ((serThread != null && serThread.isAlive()) || + (gpsdThread != null && gpsdThread.isAlive()) || + (oldGpsdThread != null && oldGpsdThread.isAlive())) + && gpsPos.isValid() ; // && gpsPos.getfiex(); } @@ -184,14 +228,137 @@ } -class GpsdThread extends mThread{ +/** + * Thread for reading data from gpsd. + * + * @author Tilman Blumenbach + */ +class GpsdThread extends mThread { + GPS gpsObj; + CWGPSPoint myGPS; + boolean run; + + + public GpsdThread(CWGPSPoint GPSPoint) throws IOException, JSONException, GPSException { + JSONObject response; + int proto_major; + + myGPS = GPSPoint; + gpsObj = new GPS(Global.getPref().gpsdHost, Global.getPref().gpsdPort); + gpsObj.stream( GPS.WATCH_ENABLE ); + + // Check major protocol version: + response = gpsObj.read(); + + if( ! response.getString( "class" ).equals( "VERSION" ) ) { + throw new GPSException( "Expected VERSION object at connect." ); + } else if( ( proto_major = response.getInt( "proto_major" ) ) != 3 ) { + throw new GPSException( "Invalid protocol API version; got " + + proto_major + ", want 3." ); + } + } + + + public void run() { + JSONObject response; + String respClass; + int noData = 0; + int notInterpreted = 0; + boolean gotValidData = false; // redundant, but compiler complains. + + run = true; + while (run) { + if( gpsObj != null ) { + gotValidData = false; + + try { + /* Tblue> This is ugly, but BufferedReader::ready() seems to + * be broken in Ewe, so instead of only polling when + * there is no data from gpsd (by checking the return + * value of GPS::waiting(), we poll on every iteration. + * Not ideal, but works for now. + */ + gpsObj.poll(); + + /* Tblue> TODO: I think this call should not block, but + * my GPS class does not yet support non-blocking + * reads... + */ + response = gpsObj.read(); + + // If we get here we have got some data: + noData = 0; + + respClass = response.getString( "class" ); + if( respClass.equals( "DEVICE" ) && response.has( "activated" ) && + response.getDouble( "activated" ) != 0 ) + { // This is a new device, we need to tell gpsd we want to watch it: + Global.getPref().log( "New GPS device, sending WATCH command." ); + gpsObj.stream( GPS.WATCH_ENABLE ); + } else if( respClass.equals( "POLL" ) ) { + gotValidData = myGPS.examineGpsd(response); + } else if( respClass.equals( "ERROR" ) ) { + Global.getPref().log( "Ignored gpsd error: " + response.getString( "message" ) ); + } + } catch( Exception e ) { + // Something bad happened, will just ignore this JSON + // object: + Global.getPref().log("Ignored Exception", e, true); + gotValidData = false; + } + + if( gotValidData ) { + notInterpreted = 0; + } else { + notInterpreted++; + } + if (notInterpreted > 22) { + myGPS.noInterpretableData(); + } + } + + try { + sleep(1000); + } catch (InterruptedException e) { + Global.getPref().log("Ignored Exception", e, true); + } + + noData++; + if (noData > 5) { + myGPS.noDataError(); + } + +/* + Vm.debug( "------------------" ); + Vm.debug( "noData : " + noData ); + Vm.debug( "notInterpreted: " + notInterpreted ); + Vm.debug( "gotValidData : " + gotValidData ); +*/ + //myGPS.printAll(); + } // while + + myGPS.noData(); + } + + + public void stop() { + run = false; + + if( gpsObj != null ) { + gpsObj.cleanup(); + } + } +} + + +class OldGpsdThread extends mThread{ Socket gpsdSocket; CWGPSPoint myGPS; boolean run, tcpForward; Socket tcpConn; String lastError = new String(); - public GpsdThread(CWGPSPoint GPSPoint) throws IOException { + public OldGpsdThread(CWGPSPoint GPSPoint) throws IOException { try{ gpsdSocket = new Socket(Global.getPref().gpsdHost, Global.getPref().gpsdPort); } catch (IOException e) { @@ -218,13 +385,15 @@ if (gpsResult!=null) { //Vm.debug("P -> " + gpsResult); noData = 0; - if (myGPS.examineGpsd(gpsResult)) + if (myGPS.examineOldGpsd(gpsResult)) notinterpreted = 0; else notinterpreted++; if (notinterpreted > 22) myGPS.noInterpretableData(); } } + + //myGPS.printAll(); } // while myGPS.noData(); } @@ -260,6 +429,7 @@ } } + /** * Thread for reading data from COM-port * Index: lib/net/ax86/GPSException.java =================================================================== --- lib/net/ax86/GPSException.java (Revision 0) +++ lib/net/ax86/GPSException.java (Revision 0) @@ -0,0 +1,49 @@ +package net.ax86; + +/** + * The GPSException is thrown by {@link GPS} when errors occur. + * + * Copyright (c) 2010 by Tilman Blumenbach. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * @author Tilman Blumenbach + * @version 1.0 + * + * @see GPS + */ +public class GPSException extends Exception +{ + public GPSException() + { + super(); + } + + + public GPSException( String message ) + { + super( message ); + } + + + public GPSException( String message, Throwable cause ) + { + super( message, cause ); + } + + + public GPSException( Throwable cause ) + { + super( cause ); + } +} Index: lib/net/ax86/GPS.java =================================================================== --- lib/net/ax86/GPS.java (Revision 0) +++ lib/net/ax86/GPS.java (Revision 0) @@ -0,0 +1,377 @@ +package net.ax86; + +import ewe.io.IOException; +import org.json.JSONException; + +import ewe.io.InputStreamReader; +import ewe.io.BufferedReader; +import ewe.io.OutputStreamWriter; +import ewe.io.BufferedWriter; +import ewe.net.Socket; + +import org.json.JSONObject; + +/** + * gpsd client library. + * + * This a simple library that allows low-level communication with the + * GPS daemon. + * + * Things that are in C libgps but not in this library (or are done in a + * different way): + * - GPS::stream(): + * - Works with JSON data only. That means there is no option to change + * the reporting format (no WATCH_JSON, WATCH_NMEA, WATCH_RARE, + * WATCH_RAW flags). + * - Always uses the new protocol (no WATCH_NEWSTYLE, WATCH_OLDSTYLE + * flags). + * - Non-blocking mode is not yet implemented (no POLL_NONBLOCK flag). + * I think this is the only real limitation of this implementation. + * + * Copyright (c) 2010 by Tilman Blumenbach. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * @author Tilman Blumenbach + * @version 0.9_ewe + * + * @see gpsd manpage for more information + */ +public class GPS +{ + /** + * The major version of this API (NOT the gpsd API!). + */ + public static final int API_MAJOR = 0; + /** + * The minor version of this API (NOT the gpsd API!). + */ + public static final int API_MINOR = 9; + + /** + * Disable watcher mode and other given WATCH_* flags. + * + * @see #stream( int ) + */ + public static final int WATCH_DISABLE = 1<<0; + /** + * Enable watcher mode and other given WATCH_* flags. + * + * @see #stream( int ) + */ + public static final int WATCH_ENABLE = 1<<1; + /** + * When reporting AIS data, scale integer quantities to floats if + * they have a divisor or rendering formula assosiated with them. + * + * @see #stream( int ) + */ + public static final int WATCH_SCALED = 1<<2; + /** + * Restrict watching to a speciied device, patch given as second + * argument to {@link #stream( int, String ) stream()}. + * + * @see #stream( int, String ) + */ + public static final int WATCH_DEVICE = 1<<3; + + /** + * DEVICE flags: GPS data has been seen on this device. + */ + public static final int SEEN_GPS = 0x01; + /** + * DEVICE flags: RTCM2 data has been seen on this device. + */ + public static final int SEEN_RTCM2 = 0x02; + /** + * DEVICE flags: RTCM3 data has been seen on this device. + */ + public static final int SEEN_RTCM3 = 0x04; + /** + * DEVICE flags: AIS data has been seen on this device. + */ + public static final int SEEN_AIS = 0x08; + + /** + * The Socket used for communication with gpsd. + */ + private Socket sock; + /** + * BufferedReader for the {@link #sock Socket}. + * + * @see #sock + */ + protected BufferedReader in; + /** + * BufferedWriter for the {@link #sock Socket}. + * + * @see #sock + */ + protected BufferedWriter out; + + /** + * A {@link GPSHook} object used for certain callbacks. + * + * @see GPSHook + * @see #setHook( GPSHook ) + */ + protected GPSHook hook = null; + + + /** + * Constructor: Creates a new GPS object and connects its + * {@link #sock Socket} to a gpsd. + * + * @param host Host name or IP address to connect to. + * @param port Port to connect to. + * + * @throws IOException E. g. on a connection failure. + * + * @see #cleanup() Call the cleanup() method when you + * do not need this object anymore. + */ + public GPS( String host, int port ) + throws IOException + { + this.sock = new Socket( host, port ); + this.in = new BufferedReader( new InputStreamReader( this.sock.getInputStream() ) ); + this.out = new BufferedWriter( new OutputStreamWriter( this.sock.getOutputStream() ) ); + } + + + /** + * Constructor: Creates a new GPS object and connects its + * {@link #sock Socket} to a gpsd. + * + * This simply calls {@link #constructor( String, int )} with the + * default gpsd port 2947. + * + * @param host Host name or IP address to connect to. + * + * @throws IOException E. g. on a connection failure. + * + * @see #constructor( String, int ) + * @see #cleanup() Call the cleanup() method when you + * do not need this object anymore. + */ + public GPS( String host ) + throws IOException + { + this( host, 2947 ); + } + + + /** + * Sends data to gpsd. + * + * @param data The data to send. + * + * @throws IOException When data could not be written to the stream. + */ + public void send( String data ) + throws IOException + { + this.out.write( data, 0, data.length() ); + // Make sure it gets through: + this.out.flush(); + } + + + /** + * Asks gpsd for data from the last-seen fixes on all active GPS devices. + * + * This is a very simple method that just calls + * {@link #send( String )} to send a "POLL" command to the + * server. + * + * @throws IOException When data could not be written to the stream. + * + * @see #send( String ) + */ + public void poll() + throws IOException + { + this.send( "?POLL;" ); + } + + + /** + * Gets the next {@linkplain JSONObject JSON object} from gpsd. + * + * This call blocks when there is no data available. Use + * {@link #waiting()} to check if there is data waiting. + * + * If there is a {@linkplain GPSHook hook} set, its + * {@link GPSHook#onRawData( JSONObject, String )} method will be called. + * + * @return A {@link JSONObject} containing the data received from gpsd. + * + * @throws IOException On a stream reading failure. + * @throws JSONException On a JSON parsing failure (e. g. syntax error). + * + * @see JSONObject + * @see #waiting() + * @see GPSHook#onRawData( JSONObject, String ) + */ + public JSONObject read() + throws IOException, JSONException + { + String rawJSON = this.in.readLine(); + JSONObject parsedJSON; + + if( rawJSON == null ) + { + throw new IOException( "End Of Stream reached" ); + } + + parsedJSON = new JSONObject( rawJSON ); + + // Call our hook, if set: + if( this.hook != null ) + { + this.hook.onRawData( parsedJSON, rawJSON ); + } + + return parsedJSON; + } + + + /** + * Checks whether there is data from gpsd to be {@linkplain #read() read}. + * + * Tblue> TODO: This does not seem to work correctly in Ewe (but it + * does with Sun's JRE). Ewe bug? + * + * @return True if there is data to be read, false otherwise. + */ + public boolean waiting() + { + try + { + return this.in.ready(); + } + catch( IOException ignored ) + { + return false; + } + } + + + /** + * Enables/tunes gpsd watcher mode. + * + * @param flags Flags describing the action(s) to take. + * @param arg A String treated as an argument to certain flags. + * + * @throws IOException E. g. on a {@link #sock Stream} write error. + * @throws JSONException If we made a huge mistake like passing a + * null String to one of the + * {@link JSONObject#put()} methods. Should not + * happen. + * @throws GPSException If arg is null but + * is needed by a flag. + * + * @see #WATCH_DISABLE + * @see #WATCH_ENABLE + * @see #WATCH_SCALED + * @see #WATCH_DEVICE + */ + public void stream( int flags, String arg ) + throws IOException, JSONException, GPSException + { + JSONObject GPSData = new JSONObject(); + + GPSData.put( "class", "WATCH" ); + + if( ( flags & WATCH_ENABLE ) != 0 ) + { + GPSData.put( "enable", true ); + + if( ( flags & WATCH_SCALED ) != 0 ) + { + GPSData.put( "scaled", true ); + } + if( ( flags & WATCH_DEVICE ) != 0 ) + { + if( arg == null ) + { + throw new GPSException( "No argument provided with WATCH_DEVICE" ); + } + + GPSData.put( "device", arg ); + } + } + else // WATCH_DISABLE? + { + GPSData.put( "enable", false ); + + if( ( flags & WATCH_SCALED ) != 0 ) + { + GPSData.put( "scaled", false ); + } + } + + // Send request to the server: + this.send( "?WATCH=" + GPSData.toString() + ";" ); + } + + + /** + * Enables/tunes gpsd watcher mode. + * + * This simply calls {@link #stream( int, String )} with an arg + * parameter of null. + * + * @param int Flags describing the action(s) to take. + * + * @throws IOException E. g. on a {@link #sock Stream} write error. + * @throws JSONException If we made a huge mistake like passing a + * null String to one of the + * {@link JSONObject#put()} methods. Should not + * happen. + * @throws GPSException If arg is null but + * is needed by a flag. + * + * @see #stream( int, String ) stream( int, String ) for information + * about flags. + */ + public void stream( int flags ) + throws IOException, JSONException, GPSException + { + this.stream( flags, null ); + } + + + /** + * Sets a {@linkplain GPSHook hook} to call on certain events. + * + * @param hook The {@link GPSHook} to set. + * @see GPSHook + */ + public void setHook( GPSHook hook ) + { + this.hook = hook; + } + + + /** + * "Destructor": Does the final clean up. + * + * Call this method when you do not this object anymore. + */ + public void cleanup() + { + // Close our socket. Also closes its Input and Output streams. + this.sock.close(); + } +} Index: lib/net/ax86/GPSTest.java =================================================================== --- lib/net/ax86/GPSTest.java (Revision 0) +++ lib/net/ax86/GPSTest.java (Revision 0) @@ -0,0 +1,95 @@ +package net.ax86; + +import ewe.sys.*; + +import net.ax86.GPSException; +import net.ax86.GPS; +import org.json.*; + +/** + * Quick and dirty (but convenient) test class/program for the {@link GPS} + * class. + * + * @author Tilman Blumenbach + */ +public class GPSTest +{ + public static void main( String args[] ) + { + JSONObject JSON = null, two; + JSONArray arr; + GPS g = null; + int maj = 0, min = 0; + + Vm.startEwe(args); + + try + { + g = new GPS( "127.0.0.1" ); + g.stream( GPS.WATCH_ENABLE ); + JSON = g.read(); + } + catch( Exception e ) + { + Vm.debug( "Could not connect to gpsd: " + e.getMessage() ); + Vm.exit(1); + } + + try + { + maj = JSON.getInt( "proto_major" ); + min = JSON.getInt( "proto_minor" ); + } + catch( JSONException e ) + { + Vm.debug( "JSON error: " + e.getMessage() ); + Vm.exit(1); + } + + Vm.debug( "Major: " + maj + ", minor: " + min ); + + try + { + while( true ) + { + // Make sure we have enough data: + //Vm.debug( ">> POLLING!" ); + + JSON = g.read(); + Vm.debug( ">> Got JSON [" + JSON.getString( "class" ) + "]: " ); + Vm.debug( ">>> " + JSON.toString() ); + + if( JSON.getString( "class" ).equals( "DEVICE" ) && + JSON.has( "activated" ) && JSON.getDouble( "activated" ) != 0 ) + { + Vm.debug( ">> Keeping up." ); + g.stream( GPS.WATCH_ENABLE ); + } + else if( JSON.getString( "class" ).equals( "POLL" ) ) + { + arr = JSON.getJSONArray( "fixes" ); + for( int i = 0; i < arr.length(); i++ ) + { + two = arr.getJSONObject( i ); + if( two.has( "lat" ) && two.has( "lon" ) ) + { + Vm.debug( ">> LAT: " + two.getDouble( "lat" ) + + " LON: " + two.getDouble( "lon" ) ); + } + } + } + + g.poll(); + Thread.sleep( 3000 ); + } + } + catch( Exception e ) + { + Vm.debug( "EOF?" ); + e.printStackTrace(); + Vm.exit(1); + } + + Vm.exit(0); + } +} Index: lib/net/ax86/GPSHook.java =================================================================== --- lib/net/ax86/GPSHook.java (Revision 0) +++ lib/net/ax86/GPSHook.java (Revision 0) @@ -0,0 +1,41 @@ +package net.ax86; + +import org.json.JSONObject; + +/** + * Hook interface supposed to be implemented by classes who want to be + * used as a "callback" on certain {@link GPS} events. + * + * Copyright (c) 2010 by Tilman Blumenbach. + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * @author Tilman Blumenbach + * @version 1.0 + * + * @see GPS + * @see GPS#setHook( GPSHook ) + */ +public interface GPSHook +{ + /** + * Gets called e. g. by {@link GPS#read()}. + * + * @param JSONObject The parsed JSON data received from gpsd. + * @param String The raw (unparsed) JSON data from gpsd. + * + * @see GPS#read() + * @see JSONObject + */ + public void onRawData( JSONObject parsedData, String rawData ); +} Index: lib/org/json/Test.java =================================================================== --- lib/org/json/Test.java (Revision 0) +++ lib/org/json/Test.java (Revision 0) @@ -0,0 +1,378 @@ +package org.json; + +import ewe.sys.*; +import ewe.sys.Double; + +import ewe.util.Enumeration; +import ewe.util.Map; +import ewe.io.StringWriter; + +import ewe.reflect.Array; +import ewe.reflect.Wrapper; + + +/** + * Test class. This file is not formally a member of the org.json library. + * It is just a casual test tool. + */ +public class Test { + + /** + * Entry point. + * @param args + */ + public static void main(String args[]) { + Vm.startEwe(args); + + Vm.debug( String.valueOf( Array.isArray( "foo" ) ) ); + + double[] ia = { 1, 6, 95, 0, -200.6 }; + int x; + Wrapper wrap = null; + + for( x = 0; x < Array.getLength( ia ); x++ ) + { + wrap = Array.getElement( ia, x, wrap ); + Vm.debug( "[" + x + "] " + String.valueOf( wrap.toJavaWrapper() ) ); + } + + Enumeration it; + JSONArray a; + JSONObject j; + Object o; + String s; + +/** + * Obj is a typical class that implements JSONString. It also + * provides some beanie methods that can be used to + * construct a JSONObject. It also demonstrates constructing + * a JSONObject with an array of names. + */ + class Obj implements JSONString { + public String aString; + public double aNumber; + public boolean aBoolean; + + public Obj(String string, double n, boolean b) { + this.aString = string; + this.aNumber = n; + this.aBoolean = b; + } + + public double getNumber() { + return this.aNumber; + } + + public String getString() { + return this.aString; + } + + public boolean isBoolean() { + return this.aBoolean; + } + + public String getBENT() { + return "All uppercase key"; + } + + public String getX() { + return "x"; + } + + public String toJSONString() { + return "{" + JSONObject.quote(this.aString) + ":" + + JSONObject.doubleToString(this.aNumber) + "}"; + } + public String toString() { + return this.getString() + " " + this.getNumber() + " " + + this.isBoolean() + "." + this.getBENT() + " " + this.getX(); + } + } + + + Obj obj = new Obj("A beany object", 42, true); + + try { + s = "[0.1]"; + a = new JSONArray(s); + Vm.debug(a.toString()); + Vm.debug(""); + + j = new JSONObject(); + o = null; + j.put("booga", o); + j.put("wooga", JSONObject.NULL); + Vm.debug(j.toString()); + Vm.debug(""); + + j = new JSONObject(); + j.increment("two"); + j.increment("two"); + Vm.debug(j.toString()); + Vm.debug(""); + + + s = "{ \"list of lists\" : [ [1, 2, 3], [4, 5, 6], ] }"; + j = new JSONObject(s); + Vm.debug(j.toString(4)); + + + j = new JSONObject(obj); + Vm.debug(j.toString()); + + s = "{ \"entity\": { \"imageURL\": \"\", \"name\": \"IXXXXXXXXXXXXX\", \"id\": 12336, \"ratingCount\": null, \"averageRating\": null } }"; + j = new JSONObject(s); + Vm.debug(j.toString(2)); + + int ar[] = {1, 2, 3}; + JSONArray ja = new JSONArray(ar); + Vm.debug(ja.toString()); + + String sa[] = {"aString", "aNumber", "aBoolean"}; + j = new JSONObject(obj, sa); + j.put("Testing JSONString interface", obj); + Vm.debug(j.toString(4)); + + j = new JSONObject("{slashes: '///', closetag: '', backslash:'\\\\', ei: {quotes: '\"\\''},eo: {a: '\"quoted\"', b:\"don't\"}, quotes: [\"'\", '\"']}"); + Vm.debug(j.toString(2)); + Vm.debug(""); + + j = new JSONObject( + "{foo: [true, false,9876543210, 0.0, 1.00000001, 1.000000000001, 1.00000000000000001," + + " .00000000000000001, 2.00, 0.1, 2e100, -32,[],{}, \"string\"], " + + " to : null, op : 'Good'," + + "ten:10} postfix comment"); + j.put("String", "98.6"); + j.put("JSONObject", new JSONObject()); + j.put("JSONArray", new JSONArray()); + j.put("int", 57); + j.put("double", 123456789012345678901234567890.); + j.put("true", true); + j.put("false", false); + j.put("null", JSONObject.NULL); + j.put("bool", "true"); + j.put("zero", -0.0); + j.put("\\u2028", "\u2028"); + j.put("\\u2029", "\u2029"); + a = j.getJSONArray("foo"); + a.put(666); + a.put(2001.99); + a.put("so \"fine\"."); + a.put("so ."); + a.put(true); + a.put(false); + a.put(new JSONArray()); + a.put(new JSONObject()); + j.put("keys", JSONObject.getNames(j)); + Vm.debug(j.toString(4)); + + Vm.debug("String: " + j.getDouble("String")); + Vm.debug(" bool: " + j.getBoolean("bool")); + Vm.debug(" to: " + j.getString("to")); + Vm.debug(" true: " + j.getString("true")); + Vm.debug(" foo: " + j.getJSONArray("foo")); + Vm.debug(" op: " + j.getString("op")); + Vm.debug(" ten: " + j.getInt("ten")); + Vm.debug(" oops: " + j.optBoolean("oops")); + + j = new JSONObject("{nix: null, nux: false, null: 'null', 'Request-URI': '/', Method: 'GET', 'HTTP-Version': 'HTTP/1.0'}"); + Vm.debug(j.toString(2)); + Vm.debug("isNull: " + j.isNull("nix")); + Vm.debug(" has: " + j.has("nix")); + Vm.debug(""); + + j = new JSONObject("{script: 'It is not allowed in HTML to send a close script tag in a stringso we insert a backslash before the /'}"); + Vm.debug(j.toString()); + Vm.debug(""); + + JSONTokener jt = new JSONTokener("{op:'test', to:'session', pre:1}{op:'test', to:'session', pre:2}"); + j = new JSONObject(jt); + Vm.debug(j.toString()); + Vm.debug("pre: " + j.optInt("pre")); + int i = jt.skipTo('{'); + Vm.debug(String.valueOf(i)); + j = new JSONObject(jt); + Vm.debug(j.toString()); + Vm.debug(""); + + a = new JSONArray(" [\"\", next is an implied null , , ok,] "); + Vm.debug(a.toString()); + Vm.debug(""); + + j = new JSONObject("{ fun => with non-standard forms ; forgiving => This package can be used to parse formats that are similar to but not stricting conforming to JSON; why=To make it easier to migrate existing data to JSON,one = [[1.00]]; uno=[[{1=>1}]];'+':+6e66 ;pluses=+++;empty = '' , 'double':0.666,true: TRUE, false: FALSE, null=NULL;[true] = [[!,@;*]]; string=> o. k. ; \r oct=0666; hex=0x666; dec=666; o=0999; noh=0x0x}"); + Vm.debug(j.toString(4)); + Vm.debug(""); + if (j.getBoolean("true") && !j.getBoolean("false")) { + Vm.debug("It's all good"); + } + + Vm.debug(""); + j = new JSONObject(j, new String[]{"dec", "oct", "hex", "missing"}); + Vm.debug(j.toString(4)); + + Vm.debug(""); + j = new JSONObject("{string: \"98.6\", long: 2147483648, int: 2147483647, longer: 9223372036854775807, double: 9223372036854775808}"); + Vm.debug(j.toString(4)); + + Vm.debug("\ngetInt"); + Vm.debug("int " + j.getInt("int")); + Vm.debug("long " + j.getInt("long")); + Vm.debug("longer " + j.getInt("longer")); + //Vm.debug("double " + j.getInt("double")); + //Vm.debug("string " + j.getInt("string")); + + Vm.debug("\ngetLong"); + Vm.debug("int " + j.getLong("int")); + Vm.debug("long " + j.getLong("long")); + Vm.debug("longer " + j.getLong("longer")); + //Vm.debug("double " + j.getLong("double")); + //Vm.debug("string " + j.getLong("string")); + + Vm.debug("\ngetDouble"); + Vm.debug("int " + j.getDouble("int")); + Vm.debug("long " + j.getDouble("long")); + Vm.debug("longer " + j.getDouble("longer")); + Vm.debug("double " + j.getDouble("double")); + Vm.debug("string " + j.getDouble("string")); + + j.put("good sized", 9223372036854775807L); + Vm.debug(j.toString(4)); + + a = new JSONArray("[2147483647, 2147483648, 9223372036854775807, 9223372036854775808]"); + Vm.debug(a.toString(4)); + + Vm.debug("\nKeys: "); + it = j.keys(); + while (it.hasMoreElements()) { + s = (String)it.nextElement(); + Vm.debug(s + ": " + j.getString(s)); + } + + + Vm.debug("\naccumulate: "); + j = new JSONObject(); + j.accumulate("stooge", "Curly"); + j.accumulate("stooge", "Larry"); + j.accumulate("stooge", "Moe"); + a = j.getJSONArray("stooge"); + a.put(5, "Shemp"); + Vm.debug(j.toString(4)); + + Map m = null; + + j = new JSONObject(m); + j.append("stooge", "Joe DeRita"); + j.append("stooge", "Shemp"); + j.accumulate("stooges", "Curly"); + j.accumulate("stooges", "Larry"); + j.accumulate("stooges", "Moe"); + j.accumulate("stoogearray", j.get("stooges")); + j.put("map", m); + Vm.debug(j.toString(4)); + + s = "{plist=Apple; AnimalSmells = { pig = piggish; lamb = lambish; worm = wormy; }; AnimalSounds = { pig = oink; lamb = baa; worm = baa; Lisa = \"Why is the worm talking like a lamb?\" } ; AnimalColors = { pig = pink; lamb = black; worm = pink; } } "; + j = new JSONObject(s); + Vm.debug(j.toString(4)); + + s = " (\"San Francisco\", \"New York\", \"Seoul\", \"London\", \"Seattle\", \"Shanghai\")"; + a = new JSONArray(s); + Vm.debug(a.toString()); + + + Vm.debug("\nTesting Exceptions: "); + + Vm.debug(">> Exception: "); + try { + a = new JSONArray("[\n\r\n\r}"); + Vm.debug(a.toString()); + } catch (Exception e) { + Vm.debug(e.toString()); + } + + Vm.debug(">> Exception: "); + try { + a = new JSONArray("<\n\r\n\r "); + Vm.debug(a.toString()); + } catch (Exception e) { + Vm.debug(e.toString()); + } + + Vm.debug(">> Exception: "); + try { + a = new JSONArray(); + a.put(Double.NEGATIVE_INFINITY); + a.put(Double.NAN); + Vm.debug(a.toString()); + } catch (Exception e) { + Vm.debug(e.toString()); + } + Vm.debug(">> Exception: "); + try { + Vm.debug(String.valueOf(j.getDouble("stooge"))); + } catch (Exception e) { + Vm.debug(e.toString()); + } + Vm.debug(">> Exception: "); + try { + Vm.debug(String.valueOf(j.getDouble("howard"))); + } catch (Exception e) { + Vm.debug(e.toString()); + } + Vm.debug(">> Exception: "); + try { + Vm.debug(j.put(null, "howard").toString()); + } catch (Exception e) { + Vm.debug(e.toString()); + } + Vm.debug(">> Exception: "); + try { + Vm.debug(String.valueOf(a.getDouble(0))); + } catch (Exception e) { + Vm.debug(e.toString()); + } + Vm.debug(">> Exception: "); + try { + Vm.debug(a.get(-1).toString()); + } catch (Exception e) { + Vm.debug(e.toString()); + } + Vm.debug(">> Exception: "); + try { + Vm.debug(a.put(Double.NAN).toString()); + } catch (Exception e) { + Vm.debug(e.toString()); + } + Vm.debug(">> Exception: "); + try { + ja = new JSONArray(new Object()); + Vm.debug(ja.toString()); + } catch (Exception e) { + Vm.debug(e.toString()); + } + + Vm.debug(">> Exception: "); + try { + s = "[)"; + a = new JSONArray(s); + Vm.debug(a.toString()); + } catch (Exception e) { + Vm.debug(e.toString()); + } + + Vm.debug(">> Exception: "); + try { + s = "{\"koda\": true, \"koda\": true}"; + j = new JSONObject(s); + Vm.debug(j.toString(4)); + } catch (Exception e) { + Vm.debug(e.toString()); + } + + } catch (Exception e) { + Vm.debug( "Exception caught:"); + e.printStackTrace(); + } + + Vm.exit(0); + } +} Index: lib/org/json/JSONObject.java =================================================================== --- lib/org/json/JSONObject.java (Revision 0) +++ lib/org/json/JSONObject.java (Revision 0) @@ -0,0 +1,1583 @@ +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 ewe.io.IOException; +import ewe.io.Writer; +import ewe.util.Iterator; +import ewe.util.Enumeration; +import ewe.util.Map; +import ewe.util.Hashtable; // Tblue> TODO: Not thread-safe? +import ewe.util.Utils; +import ewe.util.StandardComparer; +import ewe.reflect.Reflect; +import ewe.reflect.Wrapper; +import ewe.reflect.Field; +import ewe.reflect.Array; + +import java.lang.reflect.Modifier; +import java.lang.reflect.Method; + +/** + * A JSONObject is an unordered collection of name/value pairs. Its + * external form is a string wrapped in curly braces with colons between the + * names and values, and commas between the values and names. The internal form + * is an object having get and opt methods for + * accessing the values by name, and put methods for adding or + * replacing values by name. The values can be any of these types: + * Boolean, JSONArray, JSONObject, + * Number, String, or the JSONObject.NULL + * object. A JSONObject constructor can be used to convert an external form + * JSON text into an internal form whose values can be retrieved with the + * get and opt methods, or to convert values into a + * JSON text using the put and toString methods. + * A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object, which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. + *

+ * The put methods adds values to an object. For example,

+ *     myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * produces the string {"JSON": "Hello, World"}. + *

+ * The texts produced by the toString methods strictly conform to + * the JSON syntax rules. + * The constructors are more forgiving in the texts they will accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing brace.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • 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: + * { } [ ] / \ : , = ; # and if they do not look like numbers + * and if they are not the reserved words true, + * false, or null.
  • + *
  • Keys can be followed by = or => as well as + * by :.
  • + *
  • Values can be followed by ; (semicolon) as + * well as by , (comma).
  • + *
  • Numbers may have the 0x- (hex) prefix.
  • + *
+ * @author JSON.org + * @author Tilman Blumenbach (modifications for Ewe) + * @version 2010-05-17_ewe + */ +public class JSONObject { + + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, + * whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null { + + /** + * There is only intended to be a single instance of the NULL object, + * so the clone method returns itself. + * @return NULL. + */ + protected final Object clone() { + return this; + } + + + /** + * A Null object is equal to the null value and to itself. + * @param object An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object + * or null. + */ + public boolean equals(Object object) { + return object == null || object == this; + } + + + /** + * Get the "null" string value. + * @return The string "null". + */ + public String toString() { + return "null"; + } + } + + + /** + * The map where the JSONObject's properties are kept. + */ + private Map map; + + + /** + * It is sometimes more convenient and less ambiguous to have a + * NULL object than to use Java's null value. + * JSONObject.NULL.equals(null) returns true. + * JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + this.map = new Hashtable(); + } + + + /** + * Construct a JSONObject from a subset of another JSONObject. + * An array of strings is used to identify the keys that should be copied. + * Missing keys are ignored. + * @param jo A JSONObject. + * @param names An array of strings. + * @throws JSONException + * @exception JSONException If a value is a non-finite number or if a name is duplicated. + */ + public JSONObject(JSONObject jo, String[] names) { + this(); + for (int i = 0; i < names.length; i += 1) { + try { + putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) { + } + } + } + + + /** + * Construct a JSONObject from a JSONTokener. + * @param x A JSONTokener object containing the source string. + * @throws JSONException If there is a syntax error in the source string + * or a duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + /* + * The key is followed by ':'. We will also tolerate '=' or '=>'. + */ + + c = x.nextClean(); + if (c == '=') { + if (x.next() != '>') { + x.back(); + } + } else if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + putOnce(key, x.nextValue()); + + /* + * Pairs are separated by ','. We will also tolerate ';'. + */ + + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + + /** + * Construct a JSONObject from a Map. + * + * @param map A map object that can be used to initialize the contents of + * the JSONObject. + * @throws JSONException + */ + public JSONObject(Map map) { + this.map = new Hashtable(); + if (map != null) { + Iterator i = map.entries(); + while (i.hasNext()) { + Map.MapEntry e = (Map.MapEntry)i.next(); + this.map.put(e.getKey(), wrap(e.getValue())); + } + } + } + + + /** + * Construct a JSONObject from an Object using bean getters. + * It reflects on all of the public methods of the object. + * For each of the methods with no parameters and a name starting + * with "get" or "is" followed by an uppercase letter, + * the method is invoked, and a key and the value returned from the getter method + * are put into the new JSONObject. + * + * The key is formed by removing the "get" or "is" prefix. + * If the second remaining character is not upper case, then the first + * character is converted to lower case. + * + * For example, if an object has a method named "getName", and + * if the result of calling object.getName() is "Larry Fine", + * then the JSONObject will contain "name": "Larry Fine". + * + * @param bean An object that has getter methods that should be used + * to make a JSONObject. + */ + public JSONObject(Object bean) { + this(); + populateMap(bean); + } + + + /** + * Construct a JSONObject from an Object, using reflection to find the + * public members. The resulting JSONObject's keys will be the strings + * from the names array, and the values will be the field values associated + * with those keys in the object. If a key is not found or not visible, + * then it will not be copied into the new JSONObject. + * @param object An object that has fields that should be used to make a + * JSONObject. + * @param names An array of strings, the names of the fields to be obtained + * from the object. + */ + public JSONObject(Object object, String names[]) { + this(); + + Reflect c = Reflect.getForObject( object ); + Wrapper wrapped = null; + + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + wrapped = c.getField(name, Reflect.PUBLIC).getValue( object, wrapped ); + putOpt(name, wrapped.toJavaWrapper()); + } catch (Exception ignore) { + } + } + } + + + /** + * Construct a JSONObject from a source JSON text string. + * This is the most commonly used JSONObject constructor. + * @param source A string beginning + * with { (left brace) and ending + * with } (right brace). + * @exception JSONException If there is a syntax error in the source + * string or a duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + + /** + * Accumulate values under a key. It is similar to the put method except + * that if there is already an object stored under the key then a + * JSONArray is stored under the key to hold all of the accumulated values. + * If there is already a JSONArray, then the new value is appended to it. + * In contrast, the put method replaces the previous value. + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the value is an invalid number + * or if the key is null. + */ + public JSONObject accumulate(String key, Object value) + throws JSONException { + testValidity(value); + Object o = opt(key); + if (o == null) { + put(key, value instanceof JSONArray ? + new JSONArray().put(value) : + value); + } else if (o instanceof JSONArray) { + ((JSONArray)o).put(value); + } else { + put(key, new JSONArray().put(o).put(value)); + } + return this; + } + + + /** + * Append values to the array under a key. If the key does not exist in the + * JSONObject, then the key is put in the JSONObject with its value being a + * JSONArray containing the value parameter. If the key was already + * associated with a JSONArray, then the value parameter is appended to it. + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the key is null or if the current value + * associated with the key is not a JSONArray. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object o = opt(key); + if (o == null) { + put(key, new JSONArray().put(value)); + } else if (o instanceof JSONArray) { + put(key, ((JSONArray)o).put(value)); + } else { + throw new JSONException("JSONObject[" + key + + "] is not a JSONArray."); + } + return this; + } + + + /** + * Produce a string from a double. The string "null" will be returned if + * the number is not finite. + * @param d A double. + * @return A String. + */ + static public String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + +// Shave off trailing zeros and decimal point, if possible. + + String s = Double.toString(d); + if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) { + while (s.endsWith("0")) { + s = s.substring(0, s.length() - 1); + } + if (s.endsWith(".")) { + s = s.substring(0, s.length() - 1); + } + } + return s; + } + + + /** + * Get the value object associated with a key. + * + * @param key A key string. + * @return The object associated with the key. + * @throws JSONException if the key is not found. + */ + public Object get(String key) throws JSONException { + Object o = opt(key); + if (o == null) { + throw new JSONException("JSONObject[" + quote(key) + + "] not found."); + } + return o; + } + + + /** + * Get the boolean value associated with a key. + * + * @param key A key string. + * @return The truth. + * @throws JSONException + * if the value is not a Boolean or the String "true" or "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object o = get(key); + 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("JSONObject[" + quote(key) + + "] is not a Boolean."); + } + + + /** + * Get the double value associated with a key. + * @param key A key string. + * @return The numeric value. + * @throws JSONException if the key is not found or + * if the value is not a Number object and cannot be converted to a number. + */ + public double getDouble(String key) throws JSONException { + Object o = get(key); + try { + return o instanceof Number ? + ((Number)o).doubleValue() : + Double.valueOf((String)o).doubleValue(); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number."); + } + } + + + /** + * Get the int value associated with a key. + * + * @param key A key string. + * @return The integer value. + * @throws JSONException if the key is not found or if the value cannot + * be converted to an integer. + */ + public int getInt(String key) throws JSONException { + Object o = get(key); + try { + return o instanceof Number ? + ((Number)o).intValue() : + Integer.parseInt((String)o); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not an int."); + } + } + + + /** + * Get the JSONArray value associated with a key. + * + * @param key A key string. + * @return A JSONArray which is the value. + * @throws JSONException if the key is not found or + * if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object o = get(key); + if (o instanceof JSONArray) { + return (JSONArray)o; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONArray."); + } + + + /** + * Get the JSONObject value associated with a key. + * + * @param key A key string. + * @return A JSONObject which is the value. + * @throws JSONException if the key is not found or + * if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object o = get(key); + if (o instanceof JSONObject) { + return (JSONObject)o; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONObject."); + } + + + /** + * Get the long value associated with a key. + * + * @param key A key string. + * @return The long value. + * @throws JSONException if the key is not found or if the value cannot + * be converted to a long. + */ + public long getLong(String key) throws JSONException { + Object o = get(key); + try { + return o instanceof Number ? + ((Number)o).longValue() : + Long.parseLong((String)o); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a long."); + } + } + + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + int length = jo.length(); + if (length == 0) { + return null; + } + Enumeration i = jo.keys(); + String[] names = new String[length]; + int j = 0; + while (i.hasMoreElements()) { + names[j] = (String)i.nextElement(); + j += 1; + } + return names; + } + + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Reflect klass = Reflect.getForObject( object ); + Field[] fields = klass.getFields( Reflect.PUBLIC ); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + + /** + * Get the string associated with a key. + * + * @param key A key string. + * @return A string which is the value. + * @throws JSONException if the key is not found. + */ + public String getString(String key) throws JSONException { + return get(key).toString(); + } + + + /** + * Determine if the JSONObject contains a specific key. + * @param key A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + + /** + * Increment a property of a JSONObject. If there is no such property, + * create one with a value of 1. If there is such a property, and if + * it is an Integer, Long, Double, or Float, then add one to it. + * @param key A key string. + * @return this. + * @throws JSONException If there is already a property with this name + * that is not an Integer, Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = opt(key); + if (value == null) { + put(key, 1); + } else { + if (value instanceof Integer) { + put(key, ((Integer)value).intValue() + 1); + } else if (value instanceof Long) { + put(key, ((Long)value).longValue() + 1); + } else if (value instanceof Double) { + put(key, ((Double)value).doubleValue() + 1); + } else if (value instanceof Float) { + put(key, ((Float)value).floatValue() + 1); + } else { + throw new JSONException("Unable to increment [" + key + "]."); + } + } + return this; + } + + + /** + * Determine if the value associated with the key is null or if there is + * no value. + * @param key A key string. + * @return true if there is no value associated with the key or if + * the value is the JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(opt(key)); + } + + + /** + * Get an enumeration of the keys of the JSONObject. + * + * @return An enumeration of the keys. + */ + public Enumeration keys() { + return ((Hashtable)(this.map)).keys(); + } + + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + + /** + * Produce a JSONArray containing the names of the elements of this + * JSONObject. + * @return A JSONArray containing the key strings, or null if the JSONObject + * is empty. + */ + public JSONArray names() { + JSONArray ja = new JSONArray(); + Enumeration keys = keys(); + while (keys.hasMoreElements()) { + ja.put(keys.nextElement()); + } + return ja.length() == 0 ? null : ja; + } + + /** + * Produce a string from a Number. + * @param n A Number + * @return A String. + * @throws JSONException If n is a non-finite number. + */ + static public String numberToString(Number n) + throws JSONException { + if (n == null) { + throw new JSONException("Null pointer"); + } + testValidity(n); + +// Shave off trailing zeros and decimal point, if possible. + + String s = n.toString(); + if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) { + while (s.endsWith("0")) { + s = s.substring(0, s.length() - 1); + } + if (s.endsWith(".")) { + s = s.substring(0, s.length() - 1); + } + } + return s; + } + + + /** + * Get an optional value associated with a key. + * @param key A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + + /** + * Get an optional boolean associated with a key. + * It returns false if there is no such key, or if the value is not + * Boolean.TRUE or the String "true". + * + * @param key A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return optBoolean(key, false); + } + + + /** + * Get an optional boolean associated with a key. + * It returns the defaultValue if there is no such key, or if it is not + * a Boolean or the String "true" or "false" (case insensitive). + * + * @param key A key string. + * @param defaultValue The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + try { + return getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get an optional double associated with a key, + * or NaN if there is no such key or if its value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return optDouble(key, Double.NaN); + } + + + /** + * Get an optional double associated with a key, or the + * defaultValue if there is no such key or if its value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + try { + Object o = opt(key); + return o instanceof Number ? ((Number)o).doubleValue() : + new Double((String)o).doubleValue(); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get an optional int value associated with a key, + * or zero if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return optInt(key, 0); + } + + + /** + * Get an optional int value associated with a key, + * or the default if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + try { + return getInt(key); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get an optional JSONArray associated with a key. + * It returns null if there is no such key, or if its value is not a + * JSONArray. + * + * @param key A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = opt(key); + return o instanceof JSONArray ? (JSONArray)o : null; + } + + + /** + * Get an optional JSONObject associated with a key. + * It returns null if there is no such key, or if its value is not a + * JSONObject. + * + * @param key A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object o = opt(key); + return o instanceof JSONObject ? (JSONObject)o : null; + } + + + /** + * Get an optional long value associated with a key, + * or zero if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return optLong(key, 0); + } + + + /** + * Get an optional long value associated with a key, + * or the default if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + try { + return getLong(key); + } catch (Exception e) { + return defaultValue; + } + } + + + /** + * Get an optional string associated with a key. + * It returns an empty string if there is no such key. If the value is not + * a string and is not null, then it is coverted to a string. + * + * @param key A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return optString(key, ""); + } + + + /** + * Get an optional string associated with a key. + * It returns the defaultValue if there is no such key. + * + * @param key A key string. + * @param defaultValue The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object o = opt(key); + return o != null ? o.toString() : defaultValue; + } + + + private void populateMap(Object bean) { + Class klass = Reflect.getForObject(bean).getReflectedClass(); + +// If klass is a System class then set includeSuperClass to false. + + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = (includeSuperClass) ? + klass.getMethods() : klass.getDeclaredMethods(); + for (int i = 0; i < methods.length; i += 1) { + try { + Method method = methods[i]; + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + if (name.equals("getClass") || + name.equals("getDeclaringClass")) { + key = ""; + } else { + key = name.substring(3); + } + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if (key.length() > 0 && + Character.isUpperCase(key.charAt(0)) && + method.getParameterTypes().length == 0) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + + key.substring(1); + } + + Object result = method.invoke(bean, (Object[])null); + + map.put(key, wrap(result)); + } + } + } catch (Exception ignore) { + } + } + } + + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key A key string. + * @param value A boolean which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException { + put(key, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + + /** + * Put a key/double pair in the JSONObject. + * + * @param key A key string. + * @param value A double which is the value. + * @return this. + * @throws JSONException If the key is null or if the number is invalid. + */ + public JSONObject put(String key, double value) throws JSONException { + put(key, new Double(value)); + return this; + } + + + /** + * Put a key/int pair in the JSONObject. + * + * @param key A key string. + * @param value An int which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + put(key, new Integer(value)); + return this; + } + + + /** + * Put a key/long pair in the JSONObject. + * + * @param key A key string. + * @param value A long which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + put(key, new Long(value)); + return this; + } + + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * @param key A key string. + * @param value A Map value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Map value) throws JSONException { + put(key, new JSONObject(value)); + return this; + } + + + /** + * Put a key/value pair in the JSONObject. If the value is null, + * then the key will be removed from the JSONObject if it is present. + * @param key A key string. + * @param value An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, + * or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is non-finite number + * or if the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + remove(key); + } + return this; + } + + + /** + * Put a key/value pair in the JSONObject, but only if the key and the + * value are both non-null, and only if there is not already a member + * with that name. + * @param key + * @param value + * @return his. + * @throws JSONException if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + put(key, value); + } + return this; + } + + + /** + * Put a key/value pair in the JSONObject, but only if the + * key and the value are both non-null. + * @param key A key string. + * @param value An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, + * or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if (key != null && value != null) { + put(key, value); + } + return this; + } + + + /** + * Produce a string in double quotes with backslash sequences in all the + * right places. A backslash will be inserted within = '\u0080' && c < '\u00a0') || + (c >= '\u2000' && c < '\u2100')) { + t = "000" + Integer.toHexString(c); + sb.append("\\u" + t.substring(t.length() - 4)); + } else { + sb.append(c); + } + } + } + sb.append('"'); + return sb.toString(); + } + + /** + * Remove a name and its value, if present. + * @param key The name to be removed. + * @return The value that was associated with the name, + * or null if there was no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Get a alphabetically sorted array of the keys in the JSONObject. + * + * Tblue> I know this is ugly and probably completely inefficient + * but blame Ewe for not providing a TreeSet. + * + * @return An array containing the keys. + */ + public Object[] sortedKeys() { + Enumeration iter = this.keys(); + Object[] keys = new Object[this.length()]; + int i; + + for( i = 0; iter.hasMoreElements(); i++ ) { + keys[i] = iter.nextElement(); + } + + // Now sort the keys: + Utils.sort( keys, new StandardComparer(), false ); + return keys; + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * @param s A String. + * @return A simple JSON value. + */ + static public Object stringToValue(String s) { + if (s.equals("")) { + return s; + } + if (s.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (s.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (s.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. + * We support the non-standard 0x- convention. + * If a number cannot be produced, then the value will just + * be a string. Note that the 0x-, plus, and implied string + * conventions are non-standard. A JSON parser may accept + * non-JSON forms as long as it accepts all correct JSON forms. + */ + + char b = s.charAt(0); + if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') { + if (b == '0' && s.length() > 2 && + (s.charAt(1) == 'x' || s.charAt(1) == 'X')) { + try { + return new Integer(Integer.parseInt(s.substring(2), 16)); + } catch (Exception ignore) { + } + } + try { + if (s.indexOf('.') > -1 || + s.indexOf('e') > -1 || s.indexOf('E') > -1) { + return Double.valueOf(s); + } else { + Long myLong = new Long(s); + if (myLong.longValue() == myLong.intValue()) { + return new Integer(myLong.intValue()); + } else { + return myLong; + } + } + } catch (Exception ignore) { + } + } + return s; + } + + + /** + * Throw an exception if the object is an NaN or infinite number. + * @param o The object to test. + * @throws JSONException If o is a non-finite number. + */ + static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double)o).isInfinite() || ((Double)o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float)o).isInfinite() || ((Float)o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } + } + } + + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * @param names A JSONArray containing a list of key strings. This + * determines the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace + * is added. If this would not result in a syntactically correct JSON text, + * then null will be returned instead. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + */ + public String toString() { + try { + Enumeration keys = keys(); + StringBuffer sb = new StringBuffer("{"); + + while (keys.hasMoreElements()) { + if (sb.length() > 1) { + sb.append(','); + } + Object o = keys.nextElement(); + sb.append(quote(o.toString())); + sb.append(':'); + sb.append(valueToString(this.map.get(o))); + } + sb.append('}'); + return sb.toString(); + } catch (Exception e) { + return null; + } + } + + + /** + * Make a prettyprinted JSON text of this JSONObject. + *

+ * 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, portable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + * @throws JSONException If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + return toString(indentFactor, 0); + } + + + /** + * Make a prettyprinted JSON text of this JSONObject. + *

+ * 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 indentation of the top level. + * @return a printable, displayable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + * @throws JSONException If the object contains an invalid number. + */ + String toString(int indentFactor, int indent) throws JSONException { + int i, j; + int n = length(); + if (n == 0) { + return "{}"; + } + Object[] keys = sortedKeys(); + StringBuffer sb = new StringBuffer("{"); + int newindent = indent + indentFactor; + Object o; + if (n == 1) { + o = keys[0]; + sb.append(quote(o.toString())); + sb.append(": "); + sb.append(valueToString(this.map.get(o), indentFactor, + indent)); + } else { + for( i = 0; i < n; i++) { + o = keys[i]; + if (sb.length() > 1) { + sb.append(",\n"); + } else { + sb.append('\n'); + } + for (j = 0; j < newindent; j += 1) { + sb.append(' '); + } + sb.append(quote(o.toString())); + sb.append(": "); + sb.append(valueToString(this.map.get(o), indentFactor, + newindent)); + } + if (sb.length() > 1) { + sb.append('\n'); + for (j = 0; j < indent; j += 1) { + sb.append(' '); + } + } + } + sb.append('}'); + return sb.toString(); + } + + + /** + * Make a JSON text of an Object value. If the object has an + * value.toJSONString() method, then that method will be used to produce + * the JSON text. The method is required to produce a strictly + * conforming text. If the object does not contain a toJSONString + * method (which is the most common case), then a text will be + * produced by other means. If the value is an array or Collection, + * then a JSONArray will be made from it and its toJSONString method + * will be called. If the value is a MAP, then a JSONObject will be made + * from it and its toJSONString method will be called. Otherwise, the + * value's toString method will be called, and the result will be quoted. + * + *

+ * Warning: This method assumes that the data structure is acyclical. + * @param value The value to be serialized. + * @return a printable, displayable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + * @throws JSONException If the value is or contains an invalid number. + */ + static String valueToString(Object value) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString)value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (o instanceof String) { + return (String)o; + } + throw new JSONException("Bad value from toJSONString: " + o); + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean || value instanceof JSONObject || + value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map) { + return new JSONObject((Map)value).toString(); + } + if (Array.isArray(value)) { + return new JSONArray(value).toString(); + } + return quote(value.toString()); + } + + + /** + * Make a prettyprinted JSON text of an object value. + *

+ * Warning: This method assumes that the data structure is acyclical. + * @param value The value to be serialized. + * @param indentFactor The number of spaces to add to each level of + * indentation. + * @param indent The indentation of the top level. + * @return a printable, displayable, transmittable + * representation of the object, beginning + * with { (left brace) and ending + * with } (right brace). + * @throws JSONException If the object contains an invalid number. + */ + static String valueToString(Object value, int indentFactor, int indent) + throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + try { + if (value instanceof JSONString) { + Object o = ((JSONString)value).toJSONString(); + if (o instanceof String) { + return (String)o; + } + } + } catch (Exception ignore) { + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean) { + return value.toString(); + } + if (value instanceof JSONObject) { + return ((JSONObject)value).toString(indentFactor, indent); + } + if (value instanceof JSONArray) { + return ((JSONArray)value).toString(indentFactor, indent); + } + if (value instanceof Map) { + return new JSONObject((Map)value).toString(indentFactor, indent); + } + if (Array.isArray(value)) { + return new JSONArray(value).toString(indentFactor, indent); + } + return quote(value.toString()); + } + + + /** + * Wrap an object, if necessary. If the object is null, return the NULL + * object. If it is an array or collection, wrap it in a JSONArray. If + * it is a map, wrap it in a JSONObject. If it is a standard property + * (Double, String, et al) then it is already wrapped. Otherwise, if it + * comes from one of the java packages, turn it into a string. And if + * it doesn't, try to wrap it in a JSONObject. If the wrapping fails, + * then null is returned. + * + * @param object The object to wrap + * @return The wrapped value + */ + static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if (object instanceof JSONObject || object instanceof JSONArray || + NULL.equals(object) || object instanceof JSONString || + object instanceof Byte || object instanceof Character || + object instanceof Short || object instanceof Integer || + object instanceof Long || object instanceof Boolean || + object instanceof Float || object instanceof Double || + object instanceof String) { + return object; + } + + if (Array.isArray(object)) { + return new JSONArray(object); + } + if (object instanceof Map) { + return new JSONObject((Map)object); + } + Package objectPackage = Reflect.getForObject(object).getReflectedClass().getPackage(); + String objectPackageName = ( objectPackage != null ? objectPackage.getName() : "" ); + if (objectPackageName.startsWith("java.") || + objectPackageName.startsWith("javax.") || + Reflect.getForObject(object).getReflectedClass().getClassLoader() == null) { + return object.toString(); + } + return new JSONObject(object); + } catch(Exception exception) { + return null; + } + } + + + /** + * Write the contents of the JSONObject as JSON text to a writer. + * For compactness, no whitespace is added. + *

+ * 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; + Enumeration keys = keys(); + writer.write('{'); + + while (keys.hasMoreElements()) { + if (b) { + writer.write(','); + } + Object k = keys.nextElement(); + writer.write(quote(k.toString())); + writer.write(':'); + Object v = this.map.get(k); + if (v instanceof JSONObject) { + ((JSONObject)v).write(writer); + } else if (v instanceof JSONArray) { + ((JSONArray)v).write(writer); + } else { + writer.write(valueToString(v)); + } + b = true; + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } +} Index: lib/org/json/JSONString.java =================================================================== --- lib/org/json/JSONString.java (Revision 0) +++ lib/org/json/JSONString.java (Revision 0) @@ -0,0 +1,18 @@ +package org.json; +/** + * The JSONString interface allows a toJSONString() + * method so that a class can change the behavior of + * JSONObject.toString(), JSONArray.toString(), + * and JSONWriter.value(Object). The + * toJSONString method will be used instead of the default behavior + * of using the Object's toString() method and quoting the result. + */ +public interface JSONString { + /** + * The toJSONString method allows a class to produce its own JSON + * serialization. + * + * @return A strictly syntactically correct JSON text. + */ + public String toJSONString(); +} Index: lib/org/json/JSONTokener.java =================================================================== --- lib/org/json/JSONTokener.java (Revision 0) +++ lib/org/json/JSONTokener.java (Revision 0) @@ -0,0 +1,435 @@ +package org.json; + +import ewe.io.BufferedReader; +import ewe.io.IOException; +import ewe.io.Reader; +import ewe.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_ewe + */ +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 + * " (double quote) or + * ' (single quote). + * @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 + "]"; + } +} Index: lib/org/json/JSONArray.java =================================================================== --- lib/org/json/JSONArray.java (Revision 0) +++ lib/org/json/JSONArray.java (Revision 0) @@ -0,0 +1,880 @@ +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 ewe.io.IOException; +import ewe.io.Writer; +import ewe.util.Iterator; +import ewe.util.Map; +import ewe.util.Vector; // Tblue> TODO: Not thread-safe? +import ewe.reflect.Wrapper; +import ewe.reflect.Array; + +/** + * 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 get and opt + * methods for accessing the values by index, and put methods for + * adding or replacing values. The values can be any of these types: + * Boolean, JSONArray, JSONObject, + * Number, String, or the + * JSONObject.NULL object. + *

+ * The constructor can convert a JSON text into a Java object. The + * toString method converts to JSON text. + *

+ * A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. + *

+ * The texts produced by the toString methods strictly conform to + * JSON syntax rules. The constructors are more forgiving in the texts they will + * accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing bracket.
  • + *
  • The null value will be inserted when there + * is , (comma) elision.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • 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: + * { } [ ] / \ : , = ; # and if they do not look like numbers + * and if they are not the reserved words true, + * false, or null.
  • + *
  • Values can be separated by ; (semicolon) as + * well as by , (comma).
  • + *
  • Numbers may have the + * 0x- (hex) prefix.
  • + *
+ + * @author JSON.org + * @author Tilman Blumenbach (modifications for Ewe) + * @version 2009-04-14_ewe + */ +public class JSONArray { + + + /** + * The Vector where the JSONArray's properties are kept. + */ + private Vector myVector; + + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.myVector = new Vector(); + } + + /** + * 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.myVector.add(null); + } else { + x.back(); + this.myVector.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 + * [ (left bracket) + * and ends with ] (right bracket). + * @throws JSONException If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + + /** + * Construct a JSONArray from an array + * @throws JSONException If not an array. + */ + public JSONArray(Object array) throws JSONException { + this(); + + Wrapper wrapped = null; + + if (Array.isArray(array)) { + int length = Array.getLength(array); + for (int i = 0; i < length; i++) { + wrapped = Array.getElement( array, i, wrapped ); + this.put(JSONObject.wrap(wrapped.toJavaWrapper())); + } + } else { + throw new JSONException( +"JSONArray initial value should be a string 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 + * separator 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.myVector.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.myVector.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.myVector.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; + } + + + /** + * 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.myVector.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 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.myVector.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.myVector.del(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. + *

+ * 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 [ (left bracket) and ending + * with ] (right bracket). + * @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.myVector.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.myVector.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. + *

+ * 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.myVector.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); + } + } +} Index: lib/org/json/JSONException.java =================================================================== --- lib/org/json/JSONException.java (Revision 0) +++ lib/org/json/JSONException.java (Revision 0) @@ -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; + } +}