FELIX-3958 Wrap plain text response in pseudo JSON

Make sure JSON response is generated even though a printer
may only support plain text

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1455133 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/inventory/src/main/java/org/apache/felix/inventory/impl/AbstractWebConsolePlugin.java b/inventory/src/main/java/org/apache/felix/inventory/impl/AbstractWebConsolePlugin.java
index 2cb6db5..4ade7f3 100644
--- a/inventory/src/main/java/org/apache/felix/inventory/impl/AbstractWebConsolePlugin.java
+++ b/inventory/src/main/java/org/apache/felix/inventory/impl/AbstractWebConsolePlugin.java
@@ -5,9 +5,9 @@
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,6 +21,7 @@
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.util.Date;
+import java.util.StringTokenizer;
 import java.util.zip.Deflater;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -45,7 +46,7 @@
 
     /**
      * Constructor
-     * 
+     *
      * @param inventoryPrinterManager The manager
      */
     AbstractWebConsolePlugin(final InventoryPrinterManagerImpl inventoryPrinterManager)
@@ -82,7 +83,7 @@
      * <p>
      * This method sets the <code>Cache-Control</code>, <code>Expires</code>,
      * and <code>Pragma</code> headers.
-     * 
+     *
      * @param response The response for which to set the cache prevention
      */
     private final void setNoCache(final HttpServletResponse response)
@@ -203,7 +204,16 @@
             response.setCharacterEncoding("UTF-8"); //$NON-NLS-1$
 
             final JSONConfigurationWriter jcw = new JSONConfigurationWriter(response.getWriter());
-            printConfigurationInventory(jcw, PrinterMode.JSON, handler);
+            final PrinterMode mode;
+            if (handler.supports(PrinterMode.JSON)) {
+                mode = PrinterMode.JSON;
+            } else {
+                mode = PrinterMode.TEXT;
+                jcw.startJSONWrapper();
+            }
+            printConfigurationInventory(jcw, mode, handler);
+            jcw.endJSONWrapper();
+            jcw.flush();
         }
         else
         {
@@ -308,9 +318,157 @@
     private static class JSONConfigurationWriter extends ConfigurationWriter
     {
 
+        private boolean wrapJSON;
+
+        private boolean startLine;
+
+        private boolean needComma;
+
         JSONConfigurationWriter(final Writer delegatee)
         {
             super(delegatee);
+            this.wrapJSON = false;
+        }
+
+        public void startJSONWrapper()
+        {
+            println("{");
+            println("  \"value\": [");
+
+            this.wrapJSON = true;
+            this.startLine = true;
+            this.needComma = false;
+        }
+
+        public void endJSONWrapper()
+        {
+            if (this.wrapJSON)
+            {
+                // properly terminate the current line
+                this.println();
+
+                this.wrapJSON = false;
+                this.startLine = false;
+
+                super.println();
+                super.println("  ]");
+                super.println("}");
+            }
+        }
+
+        // IE has an issue with white-space:pre in our case so, we write
+        // <br/> instead of [CR]LF to get the line break. This also works
+        // in other browsers.
+        public void println()
+        {
+            if (wrapJSON)
+            {
+                if (!this.startLine)
+                {
+                    super.write('"');
+                    this.startLine = true;
+                    this.needComma = true;
+                }
+            }
+            else
+            {
+                super.println();
+            }
+        }
+
+        // some VM implementation directly write in underlying stream, instead
+        // of
+        // delegation to the write() method. So we need to override this, to
+        // make
+        // sure, that everything is escaped correctly
+        public void print(final String str)
+        {
+            final char[] chars = str.toCharArray();
+            write(chars, 0, chars.length);
+        }
+
+        private final char[] oneChar = new char[1];
+
+        // always delegate to write(char[], int, int) otherwise in some VM
+        // it cause endless cycle and StackOverflowError
+        public void write(final int character)
+        {
+            synchronized (oneChar)
+            {
+                oneChar[0] = (char) character;
+                write(oneChar, 0, 1);
+            }
+        }
+
+        // write the characters unmodified unless filtering is enabled in
+        // which case the writeFiltered(String) method is called for filtering
+        public void write(char[] chars, int off, int len)
+        {
+            if (this.wrapJSON)
+            {
+                if (this.startLine)
+                {
+                    this.startLine();
+                    this.startLine = false;
+                }
+
+                String v = new String(chars, off, len);
+                StringTokenizer st = new StringTokenizer(v, "\r\n\"", true);
+                while (st.hasMoreTokens())
+                {
+                    String t = st.nextToken();
+                    if (t.length() == 1)
+                    {
+                        char c = t.charAt(0);
+                        if (c == '\r')
+                        {
+                            // ignore
+                        }
+                        else if (c == '\n')
+                        {
+                            this.println();
+                            this.startLine();
+                        }
+                        else if (c == '"')
+                        {
+                            super.write('\\');
+                            super.write(c);
+                        }
+                        else
+                        {
+                            super.write(c);
+                        }
+                    }
+                    else
+                    {
+                        super.write(t.toCharArray(), 0, t.length());
+                    }
+                }
+            }
+            else
+            {
+                super.write(chars, off, len);
+            }
+        }
+
+        // write the string unmodified unless filtering is enabled in
+        // which case the writeFiltered(String) method is called for filtering
+        public void write(final String string, final int off, final int len)
+        {
+            write(string.toCharArray(), off, len);
+        }
+
+        private void startLine()
+        {
+            if (this.needComma)
+            {
+                super.write(',');
+                super.println();
+                this.needComma = false;
+            }
+
+            super.write("    \"".toCharArray(), 0, 5);
+            this.startLine = false;
         }
     }
 
@@ -394,8 +552,8 @@
 
         /**
          * Escapes HTML special chars like: <>&\r\n and space
-         * 
-         * 
+         *
+         *
          * @param text the text to escape
          * @return the escaped text
          */