source: trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorServlet.java @ 1022

Last change on this file since 1022 was 1022, checked in by pharms, 12 years ago
  • removed todos
File size: 29.6 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.htmlmonitor;
16
17import java.io.BufferedReader;
18import java.io.FileNotFoundException;
19import java.io.IOException;
20import java.io.InputStream;
21import java.io.InputStreamReader;
22import java.io.PrintWriter;
23import java.net.MalformedURLException;
24import java.net.URL;
25import java.util.ArrayList;
26import java.util.List;
27import java.util.Map;
28import java.util.Set;
29
30import javax.servlet.ServletException;
31import javax.servlet.http.HttpServletRequest;
32import javax.servlet.http.HttpServletResponse;
33
34import org.json.simple.JSONArray;
35import org.json.simple.JSONObject;
36import org.json.simple.JSONValue;
37import org.json.simple.parser.ParseException;
38import org.mortbay.jetty.servlet.DefaultServlet;
39
40import de.ugoe.cs.util.FileTools;
41import de.ugoe.cs.util.console.Console;
42
43/**
44 * <p>
45 * the servlet deployed in the web server that receives all client messages and returns the client
46 * java script. The messages are parsed, validated, and forwarded to the provided message listener.
47 * If a message is not valid, it is discarded. If an event in a message is not valid, it is
48 * discarded. Messages are only received via the POST HTTP method. The GET HTTP method is only
49 * implemented for returning the client java script.
50 * </p>
51 *
52 * @author Patrick Harms
53 */
54class HtmlMonitorServlet extends DefaultServlet {
55
56    /**  */
57    private static final long serialVersionUID = 1L;
58   
59    /**  */
60    private static final boolean DO_TRACE = false;
61   
62    /**
63     * <p>
64     * Name and path of the robot filter.
65     * </p>
66     */
67    private static final String ROBOTFILTERFILE = "data/robots/robotfilter.txt";
68
69    /**
70     * <p>
71     * Field that contains a regular expression that matches all robots
72     * contained in {@link #ROBOTFILTERFILE}.
73     * </p>
74     */
75    private String robotRegex = null;
76
77    /**
78     * the message listener to forward received messages to.
79     */
80    private transient HtmlMonitorMessageListener messageListener;
81
82    /**
83     * <p>
84     * initializes the servlet with the message listener to which all events shall be forwarded
85     * </p>
86     *
87     * @param messageListener the message listener that shall receive all client events
88     */
89    HtmlMonitorServlet(HtmlMonitorMessageListener messageListener) {
90        this.messageListener = messageListener;
91        try {
92            loadRobotRegex();
93        }
94        catch (Exception e) {
95            Console.println
96                ("robot filtering disabled: could not parse robot filter file " + ROBOTFILTERFILE);
97        }
98    }
99
100    /* (non-Javadoc)
101     * @see org.mortbay.jetty.servlet.DefaultServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
102     */
103    @Override
104    protected void doGet(HttpServletRequest request, HttpServletResponse response)
105        throws ServletException, IOException
106    {
107        if ((request.getPathInfo() != null) &&
108            (request.getPathInfo().endsWith("/script/autoquest-htmlmonitor.js")))
109        {
110            BufferedReader reader = null;
111           
112            try {
113                InputStream script = this.getClass().getClassLoader().getResourceAsStream
114                     ("autoquest-htmlmonitor.js");
115               
116                if (script == null) {
117                    Console.printerrln("could not read autoquest-htmlmonitor.js from classpath");
118                }
119                else {
120                    reader = new BufferedReader(new InputStreamReader(script, "UTF-8"));
121                    PrintWriter output = response.getWriter();
122                    String line;
123                   
124                    while ((line = reader.readLine()) != null) {
125                        output.println(line);
126                    }
127                   
128                    output.close();
129                }
130            }
131            catch (Exception e) {
132                Console.printerrln("could not read autoquest-htmlmonitor.js from classpath: " + e);
133                Console.logException(e);
134            }
135            finally {
136                if (reader != null) {
137                    reader.close();
138                }
139            }
140        }
141    }
142
143    /* (non-Javadoc)
144     * @see org.mortbay.jetty.servlet.DefaultServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
145     */
146    @Override
147    protected void doPost(HttpServletRequest request, HttpServletResponse response)
148        throws ServletException, IOException
149    {
150        Object value = null;
151        try {
152            //InputStream requestInputStream = dumpStreamContent(request.getInputStream());
153            InputStream requestInputStream = request.getInputStream();
154
155            value = JSONValue.parseWithException
156                (new InputStreamReader(requestInputStream, "UTF-8"));
157           
158            if (!(value instanceof JSONObject)) {
159                Console.printerrln("incoming data is not of the expected type --> discarding it");
160            }
161            else {
162                handleJSONObject((JSONObject) value);
163            }
164        }
165        catch (ParseException e) {
166            Console.printerrln
167                ("could not parse incoming data --> discarding it (" + e.toString() + ")");
168        }
169    }
170
171    /**
172     * <p>
173     * processes a received JSON object and validates it. If the message is ok, it is forwarded
174     * to the message listener
175     * </p>
176     *
177     * @param object the JSON object that contains a client message
178     */
179    private void handleJSONObject(JSONObject object) {
180        if (DO_TRACE) {
181            dumpJSONObject(object, "");
182        }
183       
184        JSONObject message = assertValue(object, "message", JSONObject.class);
185       
186        if (message == null) {
187            Console.printerrln("incoming data is no valid message --> discarding it");
188        }
189        else {
190            HtmlClientInfos clientInfos = extractClientInfos(message);
191
192            if (clientInfos == null) {
193                Console.printerrln
194                    ("incoming message does not contain valid client infos --> discarding it");
195            }
196            else if (isRobot(clientInfos.getUserAgent())) {
197                Console.printerrln
198                    ("ignoring robot " + clientInfos.getUserAgent());
199            }
200            else {
201                HtmlPageElement guiStructure = extractHtmlPageElements(message, clientInfos);
202                HtmlEvent[] events = extractHtmlEvents(message, clientInfos);
203               
204                if (events == null) {
205                    Console.printerrln
206                        ("incoming message does not contain valid events --> discarding it");
207                }
208                else {
209                    messageListener.handleMessage(clientInfos, guiStructure, events);
210                }
211            }
212        }
213    }
214
215    /**
216     * <p>
217     * tries to extract the client infos out of the received JSON object. If this is not fully
218     * possible, an appropriate message is dumped and the whole message is discarded (the method
219     * return null).
220     * </p>
221     *
222     * @param message the message to parse the client infos from
223     *
224     * @return the client infos, if the message is valid in this respect, or null if not
225     */
226    private HtmlClientInfos extractClientInfos(JSONObject message) {
227        HtmlClientInfos clientInfos = null;
228       
229        JSONObject infos = assertValue(message, "clientInfos", JSONObject.class);
230       
231        if (infos != null) {
232            String clientId = assertValue((JSONObject) infos, "clientId", String.class);
233            String userAgent = assertValue((JSONObject) infos, "userAgent", String.class);
234            URL url = assertValue((JSONObject) infos, "url", URL.class);
235            String title = assertValue((JSONObject) infos, "title", String.class);
236           
237            if (clientId == null) {
238                Console.printerrln("client infos do not contain a valid client id");
239            }
240            else if (userAgent == null) {
241                Console.printerrln("client infos do not contain a valid user agent");
242            }
243            else if (url == null) {
244                Console.printerrln("client infos do not contain a valid URL");
245            }
246            else if (title == null) {
247                Console.printerrln("client infos do not contain a valid title");
248            }
249            else {
250                clientInfos = new HtmlClientInfos(clientId, userAgent, url, title);
251            }
252        }
253       
254        return clientInfos;
255    }
256
257    /**
258     * <p>
259     * tries to extract the events out of the received JSON object. If this is not fully
260     * possible, an appropriate message is dumped and the errorprone event is discarded. If no
261     * valid event is found, the whole message is discarded.
262     * </p>
263     *
264     * @param object      the message to parse the events from
265     * @param clientInfos the infos about the client that send the events
266     * 
267     * @return the valid events stored in the message, or null if there are none
268     */
269    private HtmlEvent[] extractHtmlEvents(JSONObject object, HtmlClientInfos clientInfos) {
270        List<HtmlEvent> events = null;
271       
272        JSONArray eventArray = assertValue(object, "events", JSONArray.class);
273       
274        if (eventArray != null) {
275            events = new ArrayList<HtmlEvent>();
276           
277            HtmlPageElement server = getServerElement(clientInfos);
278            HtmlPageElement page = getPageElementRepresentingWebPage(clientInfos, server);
279
280            for (int i = 0; i < eventArray.size(); i++) {
281                Object eventObj = eventArray.get(i);
282                if (!(eventObj instanceof JSONObject)) {
283                    Console.printerrln("event number " + (i + 1) + " is not a valid event object");
284                }
285                else {
286                    Long time = assertValue(((JSONObject) eventObj), "time", Long.class);
287                    String path = assertValue(((JSONObject) eventObj), "path", String.class);
288                    String eventType =
289                        assertValue(((JSONObject) eventObj), "eventType", String.class);
290                    Integer[] coordinates =
291                        assertValue(((JSONObject) eventObj), "coordinates", Integer[].class);
292                    Integer key = assertValue(((JSONObject) eventObj), "key", Integer.class);
293                    Integer[] scrollPosition =
294                        assertValue(((JSONObject) eventObj), "scrollPosition", Integer[].class);
295                    String selectedValue =
296                            assertValue(((JSONObject) eventObj), "selectedValue", String.class);
297                   
298                    if (eventType == null) {
299                        Console.printerrln("event number " + (i + 1) + " has no valid event type");
300                    }
301                    else if (time == null) {
302                        Console.printerrln(eventType + " event has no valid timestamp");
303                    }
304                    else if (path == null) {
305                        Console.printerrln(eventType + " event has no valid path");
306                    }
307                    else if ((coordinates != null) && (coordinates.length != 2)) {
308                        Console.printerrln(eventType + " event has no valid coordinates");
309                    }
310                    else if (checkEventParameterCombinations
311                                (eventType, coordinates, key, scrollPosition, selectedValue))
312                    {
313                        path = page.getPath() + path;
314                        events.add(new HtmlEvent(clientInfos, time, path, eventType,
315                                                 coordinates, key, scrollPosition, selectedValue));
316                    }
317                    else {
318                        Console.printerrln(eventType + " event has no valid parameter combination");
319                    }
320                }
321            }
322           
323        }
324       
325        if ((events != null) && (events.size() > 0)) {
326            return events.toArray(new HtmlEvent[events.size()]);
327        }
328        else {
329            return null;
330        }
331    }
332
333    /**
334     * <p>
335     * extracts the GUI structure from the provided JSON object.
336     * </p>
337     *
338     * @param object      the JSON object to extract the GUI structure from
339     * @param clientInfos infos about the client who send the data
340     *
341     * @return the GUI structure extracted from the JSON object of which the root node is a
342     *         representation of the server of the HTML page that was observed
343     */
344    private HtmlPageElement extractHtmlPageElements(JSONObject      object,
345                                                    HtmlClientInfos clientInfos)
346    {
347        HtmlPageElement server = getServerElement(clientInfos);
348        HtmlPageElement page = getPageElementRepresentingWebPage(clientInfos, server);
349
350        JSONObject jsonPageElement = assertValue(object, "guiModel", JSONObject.class);
351        page.addChild(convert(jsonPageElement, page.getPath()));
352       
353        return server;
354    }
355
356    /**
357     * <p>
358     * instantiates an element of the GUI structure representing the server of the observed
359     * web page
360     * </p>
361     *
362     * @param clientInfos infos about the client who send the data
363     *
364     * @return as described
365     */
366    private HtmlPageElement getServerElement(HtmlClientInfos clientInfos) {
367        String id = clientInfos.getUrl().getHost();
368        if (clientInfos.getUrl().getPort() > -1) {
369            id += ":" + clientInfos.getUrl().getPort();
370        }
371       
372        return new HtmlPageElement(null, "server", id, 0);
373    }
374
375    /**
376     * <p>
377     * instantiates an element of the GUI structure representing the observed web page. Adds
378     * this element to the provided server as child.
379     * </p>
380     *
381     * @param clientInfos infos about the client who send the data
382     * @param server      the server on which the page represented by the return value resists
383     *
384     * @return as described
385     */
386    private HtmlPageElement getPageElementRepresentingWebPage(HtmlClientInfos clientInfos,
387                                                              HtmlPageElement server)
388    {
389        String id = clientInfos.getUrl().getPath();
390       
391        if (clientInfos.getUrl().getQuery() != null) {
392            id += "?" + clientInfos.getUrl().getQuery();
393        }
394       
395        HtmlPageElement page =
396            new HtmlPageElement(server.getPath(), "document", id, clientInfos.getTitle(), 0);
397       
398        server.addChild(page);
399       
400        return page;
401    }
402
403    /**
404     * <p>
405     * converts a JSON object representing an HTML page element to an HTML page element. Calls
406     * itself recursively to also convert the children of the element, if any.
407     * </p>
408     *
409     * @param jsonPageElement the JSON object to be converted
410     * @param parentPath      the path through the DOM of the parent HTML page element of the
411     *                        converted one
412     *                       
413     * @return as described.
414     */
415    private HtmlPageElement convert(JSONObject jsonPageElement, String parentPath) {
416        HtmlPageElement result = null;
417
418        if (jsonPageElement != null) {
419            String tagName = assertValue(jsonPageElement, "tagName", String.class);
420            String id = assertValue(jsonPageElement, "id", String.class);
421            Integer index = assertValue(jsonPageElement, "index", Integer.class);
422
423            result = new HtmlPageElement(parentPath, tagName, id, index);
424
425            JSONArray childElements = assertValue(jsonPageElement, "children", JSONArray.class);
426           
427            if (childElements != null) {
428                Object jsonChild;
429
430                for (int i = 0; i < childElements.size(); i++) {
431                    jsonChild = childElements.get(i);
432                    if (!(jsonChild instanceof JSONObject)) {
433                        Console.printerrln("child " + (i + 1) + " of HTML page element " + tagName +
434                                           " is no valid HTML page element");
435                    }
436                    else {
437                        result.addChild(convert((JSONObject) jsonChild, result.getPath()));
438                    }
439                }
440            }
441           
442        }
443       
444        return result;   
445    }
446
447    /**
448     * <p>
449     * validates if for the given event type the parameter combination of coordinates, key,
450     * scroll position, and selected value is valid. As an example, an onclick event should
451     * usually not have an associated scroll position.
452     * </p>
453     *
454     * @param eventType      the type of the event
455     * @param coordinates    the coordinates of the event
456     * @param key            the key of the event
457     * @param scrollPosition the scroll position of the event
458     * @param selectedValue  the value selected through a specific event
459     *
460     * @return true, if the combination of the parameters is valid, false else
461     */
462    private boolean checkEventParameterCombinations(String    eventType,
463                                                    Integer[] coordinates,
464                                                    Integer   key,
465                                                    Integer[] scrollPosition,
466                                                    String    selectedValue)
467    {
468        boolean result = false;
469       
470        if ("onscroll".equals(eventType)) {
471            if ((coordinates == null) && (key == null) &&
472                (scrollPosition != null) && (selectedValue == null))
473            {
474                result = true;
475            }
476            else {
477                Console.printerrln(eventType + " event has invalid parameters");
478            }
479        }
480        else if ("onclick".equals(eventType) || "ondblclick".equals(eventType)) {
481            if ((coordinates != null) && (key == null) &&
482                (scrollPosition == null) && (selectedValue == null))
483            {
484                result = true;
485            }
486            else {
487                Console.printerrln(eventType + " event has invalid parameters");
488            }
489        }
490        else if ("onchange".equals(eventType)) {
491            if ((coordinates == null) && (key == null) &&
492                (scrollPosition == null) && (selectedValue != null))
493            {
494                result = true;
495            }
496            else {
497                Console.printerrln(eventType + " event has invalid parameters");
498            }
499        }
500        else if ("onkeypress".equals(eventType) || "onkeydown".equals(eventType) ||
501                 "onkeyup".equals(eventType))
502        {
503            if ((coordinates == null) && (key != null) &&
504                (scrollPosition == null) && (selectedValue == null))
505            {
506                result = true;
507            }
508            else {
509                Console.printerrln(eventType + " event has invalid parameters");
510            }
511        }
512        else if ("onfocus".equals(eventType) || "onmouseout".equals(eventType) ||
513                 "onmousemove".equals(eventType) || "onload".equals(eventType) ||
514                 "onunload".equals(eventType) || "onbeforeunload".equals(eventType) ||
515                 "onpagehide".equals(eventType) || "onpageshow".equals(eventType) ||
516                 "onabort".equals(eventType) || "onsubmit".equals(eventType) ||
517                 "onplaying".equals(eventType) || "onpause".equals(eventType) ||
518                 "ontimeupdate".equals(eventType) || "onerror".equals(eventType) ||
519                 "onundo".equals(eventType) || "onreset".equals(eventType) ||
520                 "onselect".equals(eventType))
521        {
522            if ((coordinates == null) && (key == null) &&
523                (scrollPosition == null) && (selectedValue == null))
524            {
525                result = true;
526            }
527            else {
528                Console.printerrln(eventType + " event has invalid parameters");
529            }
530        }
531        else {
532            Console.printerrln("'" + eventType + "' is not a valid event type");
533        }
534       
535        return result;
536    }
537
538    /**
539     * <p>
540     * converts a value in the provided object matching the provided key to the provided type. If
541     * there is no value with the key or if the value can not be transformed to the provided type,
542     * the method returns null.
543     * </p>
544     *
545     * @param object the object to read the value from
546     * @param key    the key of the value
547     * @param clazz  the type to which the value should be transformed
548     *
549     * @return the value or null if either the value does not exist or if it can not be transformed
550     *         to the expected type
551     */
552    @SuppressWarnings("unchecked")
553    private <T> T assertValue(JSONObject object, String key, Class<T> clazz) {
554        Object value = object.get(key);
555        T result = null;
556       
557        if (clazz.isInstance(value)) {
558            result = (T) value;
559        }
560        else if (value instanceof String) {
561            if (URL.class.equals(clazz)) {
562                try {
563                    result = (T) new URL((String) value);
564                }
565                catch (MalformedURLException e) {
566                    e.printStackTrace();
567                    Console.printerrln("retrieved malformed URL for key '" + key + "': " + value +
568                                       " (" + e.toString() + ")");
569                }
570            }
571            else if ((int.class.equals(clazz)) || (Integer.class.equals(clazz))) {
572                try {
573                    result = (T) Integer.valueOf(Integer.parseInt((String) value));
574                }
575                catch (NumberFormatException e) {
576                    Console.printerrln
577                        ("retrieved malformed integer for key '" + key + "': " + value);
578                }
579            }
580            else if ((long.class.equals(clazz)) || (Long.class.equals(clazz))) {
581                try {
582                    result = (T) Long.valueOf(Long.parseLong((String) value));
583                }
584                catch (NumberFormatException e) {
585                    Console.printerrln
586                        ("retrieved malformed long for key '" + key + "': " + value);
587                }
588            }
589        }
590        else if (value instanceof Long) {
591            if ((int.class.equals(clazz)) || (Integer.class.equals(clazz))) {
592                result = (T) (Integer) ((Long) value).intValue();
593            }
594        }
595        else if (value instanceof JSONArray) {
596            if ((int[].class.equals(clazz)) || (Integer[].class.equals(clazz))) {
597                Integer[] resultArray = new Integer[((JSONArray) value).size()];
598                boolean allCouldBeParsed = true;
599               
600                for (int i = 0; i < ((JSONArray) value).size(); i++) {
601                    try {
602                        if (((JSONArray) value).get(i) instanceof Long) {
603                            resultArray[i] = (int) (long) (Long) ((JSONArray) value).get(i);
604                        }
605                        else if (((JSONArray) value).get(i) instanceof String) {
606                            try {
607                                resultArray[i] =
608                                    (int) Long.parseLong((String) ((JSONArray) value).get(i));
609                            }
610                            catch (NumberFormatException e) {
611                                Console.printerrln
612                                    ("retrieved malformed integer array for key '" + key + "': " +
613                                     value);
614                       
615                                allCouldBeParsed = false;
616                                break;
617                            }
618                        }
619                        else {
620                            Console.printerrln
621                                ("can not handle type of value in expected integer array '" + key +
622                                 "': " + value);
623                        }
624                    }
625                    catch (ClassCastException e) {
626                        e.printStackTrace();
627                        Console.printerrln("expected integer array for key '" + key +
628                                           "' but it was something else: " + value);
629                       
630                        allCouldBeParsed = false;
631                        break;
632                    }
633                }
634               
635                if (allCouldBeParsed) {
636                    result = (T) resultArray;
637                }
638            }
639        }
640       
641        return result;
642    }
643
644    /**
645     * <p>
646     * Checks whether an agent is a robot.
647     * </p>
648     *
649     * @param agent
650     *            agent that is checked
651     * @return true, if the agent is a robot; false otherwise
652     */
653    private boolean isRobot(String agent) {
654        return agent.matches(robotRegex);
655    }
656
657    /**
658     * <p>
659     * Reads {@link #ROBOTFILTERFILE} and creates a regular expression that
660     * matches all the robots defined in the file. The regular expression is
661     * stored in the field {@link #robotRegex}.
662     * </p>
663     *
664     * @throws IOException
665     *             thrown if there is a problem reading the robot filter
666     * @throws FileNotFoundException
667     *             thrown if the robot filter is not found
668     */
669    private void loadRobotRegex() throws IOException, FileNotFoundException {
670        String[] lines = FileTools.getLinesFromFile(ROBOTFILTERFILE);
671        StringBuilder regex = new StringBuilder();
672        for (int i = 0; i < lines.length; i++) {
673            regex.append("(.*" + lines[i] + ".*)");
674            if (i != lines.length - 1) {
675                regex.append('|');
676            }
677        }
678        robotRegex = regex.toString();
679    }
680
681    /**
682     * <p>
683     * convenience method for dumping the content of a stream and returning a new stream
684     * containing the same data.
685     * </p>
686     *
687     * @param inputStream the stream to be dumped and copied
688     * @return the copy of the stream
689     *
690     * @throws IOException if the stream can not be read
691     */
692/*    private InputStream dumpStreamContent(ServletInputStream inputStream) throws IOException {
693        List<Byte> bytes = new ArrayList<Byte>();
694        int buf;
695       
696        while ((buf = inputStream.read()) >= 0) {
697            bytes.add((byte) buf);
698        }
699       
700        byte[] byteArray = new byte[bytes.size()];
701        for (int i = 0; i < bytes.size(); i++) {
702            byteArray[i] = bytes.get(i);
703        }
704       
705        System.out.println(new String(byteArray, "UTF-8"));
706       
707        return new ByteArrayInputStream(byteArray);
708    }*/
709
710    /**
711     * <p>
712     * convenience method for dumping an object to std out. If the object is a JSON object, it is
713     * deeply analyzed and its internal structure is dumped as well.
714     * </p>
715     *
716     * @param object the object to dump
717     * @param indent the indentation to be used.
718     */
719    private void dumpJSONObject(Object object, String indent) {
720        if (object instanceof JSONArray) {
721            boolean arrayContainsJSONObjects = false;
722            for (Object arrayElem : (JSONArray) object) {
723                if (arrayElem instanceof JSONObject) {
724                    arrayContainsJSONObjects = true;
725                    break;
726                }               
727            }
728           
729            if (arrayContainsJSONObjects) {
730                System.out.println();
731                System.out.print(indent);
732                System.out.println('[');
733                System.out.print(indent);
734                System.out.print(' ');
735            }
736            else {
737                System.out.print(' ');
738                System.out.print('[');
739            }
740           
741            int index = 0;
742            for (Object arrayElem : (JSONArray) object) {
743                if (index++ > 0) {
744                    System.out.print(",");
745                    if (arrayContainsJSONObjects) {
746                        System.out.println();
747                        System.out.print(indent);
748                    }
749
750                    System.out.print(' ');
751                }
752
753                dumpJSONObject(arrayElem, indent + "  ");
754            }
755           
756            if (arrayContainsJSONObjects) {
757                System.out.println();
758                System.out.print(indent);
759            }
760           
761            System.out.print(']');
762        }
763        else if (object instanceof JSONObject) {
764            System.out.println(" {");
765           
766            @SuppressWarnings("unchecked")
767            Set<Map.Entry<?,?>> entrySet = ((JSONObject) object).entrySet();
768           
769            int index = 0;
770            for (Map.Entry<?,?> entry : entrySet) {
771                if (index++ > 0) {
772                    System.out.println(",");
773                }
774                System.out.print(indent);
775                System.out.print("  \"");
776                System.out.print(entry.getKey());
777                System.out.print("\":");
778                dumpJSONObject(entry.getValue(), indent + "  ");
779            }
780           
781            System.out.println();
782            System.out.print(indent);
783            System.out.print('}');
784        }
785        else {
786            System.out.print('"');
787            System.out.print(object);
788            System.out.print('"');
789        }
790    }
791
792}
Note: See TracBrowser for help on using the repository browser.