You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2018/06/09 13:42:09 UTC

[1/2] lucene-solr:branch_7x: SOLR-12455: Refactor JSON serialization code into SolrJ package

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_7x 56559f089 -> fef87d718


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/solrj/src/java/org/apache/solr/common/util/WriteableValue.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/WriteableValue.java b/solr/solrj/src/java/org/apache/solr/common/util/WriteableValue.java
new file mode 100644
index 0000000..82e7d00
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/common/util/WriteableValue.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.common.util;
+
+import java.io.IOException;
+
+import org.apache.solr.common.util.JavaBinCodec.ObjectResolver;
+
+public abstract class WriteableValue implements ObjectResolver {
+  public abstract void write(String name, TextWriter writer) throws IOException;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/solrj/src/test/org/apache/solr/client/solrj/cloud/autoscaling/TestPolicy.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/cloud/autoscaling/TestPolicy.java b/solr/solrj/src/test/org/apache/solr/client/solrj/cloud/autoscaling/TestPolicy.java
index 60c84b7..30506c2 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/cloud/autoscaling/TestPolicy.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/cloud/autoscaling/TestPolicy.java
@@ -52,6 +52,7 @@ import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.CollectionParams.CollectionAction;
 import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.JsonTextWriter;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.ObjectCache;
 import org.apache.solr.common.util.Pair;
@@ -2525,7 +2526,7 @@ public void testUtilizeNodeFailure2() throws Exception {
     StringWriter writer = new StringWriter();
     NamedList<Object> val = new NamedList<>();
     val.add("violations", violations);
-    JSONWriter.write (writer, true, JSONWriter.JSON_NL_MAP, val);
+    JSONWriter.write (writer, true, JsonTextWriter.JSON_NL_MAP, val);
 
     assertEquals(2l,
         Utils.getObjectByPath(Utils.fromJSONString(writer.toString()), true,"violations[0]/violation/replica/NRT"));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/solrj/src/test/org/apache/solr/common/util/TestFastWriter.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/common/util/TestFastWriter.java b/solr/solrj/src/test/org/apache/solr/common/util/TestFastWriter.java
new file mode 100644
index 0000000..5b240fe
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/common/util/TestFastWriter.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.common.util;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.lucene.util.LuceneTestCase;
+
+
+class MemWriter extends FastWriter {
+  public List<char[]> buffers = new LinkedList<>();
+
+  Random r;
+  public MemWriter(char[] tempBuffer, Random r) {
+    super(null, tempBuffer, 0);
+    this.r = r;
+  }
+
+  @Override
+  public void flush(char[] arr, int offset, int len) throws IOException {
+    if (arr == buf && offset==0 && len==buf.length) {
+      buffers.add(buf);  // steal the buffer
+      buf = new char[r.nextInt(9000)+1];
+    } else if (len > 0) {
+      char[] newBuf = new char[len];
+      System.arraycopy(arr, offset, newBuf, 0, len);
+      buffers.add(newBuf);
+    }
+  }
+
+  @Override
+  public void flush(String str, int offset, int len) throws IOException {
+    if (len == 0) return;
+    buffers.add( str.substring(offset, offset+len).toCharArray() );
+  }
+}
+
+
+
+public class TestFastWriter extends LuceneTestCase {
+
+  Random rand;
+  char[] arr;
+  String s;
+
+  public void testRandomWrites() throws Exception {
+    rand = random();
+
+    arr = new char[20000];
+    for (int i=0; i<arr.length; i++) {
+      arr[i] = (char)rand.nextInt();
+    }
+    s = new String(arr);
+
+    for (int i=0; i<1000; i++) {
+      doRandomWrites();
+    }
+  }
+
+
+  public void doRandomWrites() throws Exception {
+    int bufSize = ( rand.nextBoolean() ? rand.nextInt(10) : rand.nextInt(20000) )+1;
+    MemWriter out = new MemWriter(new char[bufSize], rand);
+
+    int hash = 0;
+    long written = 0;
+    int iter = rand.nextInt(20)+1;
+    for (int i=0; i<iter; i++) {
+      int which = rand.nextInt(3);
+
+
+      int off = rand.nextInt(arr.length);
+      int len = off < arr.length ? rand.nextInt(arr.length - off) : 0;
+
+
+
+      if (which == 0) {
+        out.write(arr, off, len);
+      } else if (which == 1) {
+        out.write(s, off, len);
+      } else {
+        len = 1;
+        out.write(arr[off]);
+      }
+
+      hash = incHash(hash, arr, off, len);
+      written += len;
+    }
+
+    out.close();
+
+    int hash2 = 0;
+    for (char[] buffer : out.buffers) {
+      hash2 = incHash(hash2, buffer, 0, buffer.length);
+    }
+
+    assertEquals(hash, hash2);
+  }
+
+
+  public int incHash(int hash, char[] arr, int off, int len) {
+    for (int i=off; i<off+len; i++) {
+      hash = hash * 31 + arr[i];
+    }
+    return hash;
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/solrj/src/test/org/apache/solr/common/util/TestSolrJsonWriter.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/common/util/TestSolrJsonWriter.java b/solr/solrj/src/test/org/apache/solr/common/util/TestSolrJsonWriter.java
new file mode 100644
index 0000000..3dd2cdd
--- /dev/null
+++ b/solr/solrj/src/test/org/apache/solr/common/util/TestSolrJsonWriter.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.common.util;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.IteratorWriter;
+import org.apache.solr.common.MapWriter;
+
+public class TestSolrJsonWriter  extends SolrTestCaseJ4 {
+  public void test() throws IOException {
+    StringWriter writer = new StringWriter();
+
+    Map<String, Object> map = new HashMap<>();
+    map.put("k1","v1");
+    map.put("k2",1);
+    map.put("k3",false);
+    map.put("k4",Utils.makeMap("k41", "v41", "k42","v42"));
+    map.put("k5", (MapWriter) ew -> {
+      ew.put("k61","v61");
+      ew.put("k62","v62");
+      ew.put("k63", (IteratorWriter) iw -> iw.add("v631")
+          .add("v632"));
+    });
+
+    new SolrJSONWriter(writer)
+        .setIndent(true)
+        .writeObj(map)
+        .close();
+    Object o = Utils.fromJSONString(writer.toString());
+    assertEquals("v1", Utils.getObjectByPath(o, true, "k1"));
+    assertEquals(1l, Utils.getObjectByPath(o, true, "k2"));
+    assertEquals(Boolean.FALSE, Utils.getObjectByPath(o, true, "k3"));
+    assertEquals("v41", Utils.getObjectByPath(o, true, "k4/k41"));
+    assertEquals("v42", Utils.getObjectByPath(o, true, "k4/k42"));
+    assertEquals("v61", Utils.getObjectByPath(o, true, "k5/k61"));
+    assertEquals("v62", Utils.getObjectByPath(o, true, "k5/k62"));
+    assertEquals("v631", Utils.getObjectByPath(o, true, "k5/k63[0]"));
+    assertEquals("v632", Utils.getObjectByPath(o, true, "k5/k63[1]"));
+  }
+}


[2/2] lucene-solr:branch_7x: SOLR-12455: Refactor JSON serialization code into SolrJ package

Posted by no...@apache.org.
SOLR-12455: Refactor JSON serialization code into SolrJ package


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/fef87d71
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/fef87d71
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/fef87d71

Branch: refs/heads/branch_7x
Commit: fef87d7181208643849b00b94f582b34748ffbb4
Parents: 56559f0
Author: Noble Paul <no...@apache.org>
Authored: Sat Jun 9 23:40:55 2018 +1000
Committer: Noble Paul <no...@apache.org>
Committed: Sat Jun 9 23:41:49 2018 +1000

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   2 +
 .../apache/solr/response/CSVResponseWriter.java |   2 +-
 .../solr/response/JSONResponseWriter.java       |   5 +-
 .../org/apache/solr/response/JSONWriter.java    | 471 +----------------
 .../apache/solr/response/PHPResponseWriter.java |   4 +-
 .../response/PHPSerializedResponseWriter.java   |   2 +-
 .../solr/response/QueryResponseWriterUtil.java  |   2 +-
 .../solr/response/RubyResponseWriter.java       |   4 +-
 .../solr/response/SmileResponseWriter.java      |   4 +-
 .../solr/response/TextResponseWriter.java       | 211 +-------
 .../apache/solr/response/WriteableValue.java    |  25 -
 .../transform/RawValueTransformerFactory.java   |   9 +-
 .../response/transform/WriteableGeoJSON.java    |   6 +-
 .../java/org/apache/solr/util/FastWriter.java   | 157 ------
 .../apache/solr/response/JSONWriterTest.java    |  53 +-
 .../org/apache/solr/util/TestFastWriter.java    | 124 -----
 .../org/apache/solr/common/util/FastWriter.java | 157 ++++++
 .../apache/solr/common/util/JsonTextWriter.java | 499 +++++++++++++++++++
 .../apache/solr/common/util/SolrJSONWriter.java | 114 +++++
 .../org/apache/solr/common/util/TextWriter.java | 231 +++++++++
 .../apache/solr/common/util/WriteableValue.java |  25 +
 .../solrj/cloud/autoscaling/TestPolicy.java     |   3 +-
 .../apache/solr/common/util/TestFastWriter.java | 125 +++++
 .../solr/common/util/TestSolrJsonWriter.java    |  60 +++
 24 files changed, 1298 insertions(+), 997 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index f4a14ca..69bf911 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -50,6 +50,8 @@ Optimizations
 
 * SOLR-12350: Do not use docValues as stored for _str (copy)fields in _default configset (janhoy)
 
+* SOLR-12455: Refactor JSON serialization code into SolrJ package (noble)
+
 Other Changes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/CSVResponseWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/CSVResponseWriter.java b/solr/core/src/java/org/apache/solr/response/CSVResponseWriter.java
index 326220c..e894c77 100644
--- a/solr/core/src/java/org/apache/solr/response/CSVResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/CSVResponseWriter.java
@@ -36,6 +36,7 @@ import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.FastWriter;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.internal.csv.CSVPrinter;
 import org.apache.solr.internal.csv.CSVStrategy;
@@ -45,7 +46,6 @@ import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.StrField;
 import org.apache.solr.search.DocList;
 import org.apache.solr.search.ReturnFields;
-import org.apache.solr.util.FastWriter;
 
 /**
  *

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/JSONResponseWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/JSONResponseWriter.java b/solr/core/src/java/org/apache/solr/response/JSONResponseWriter.java
index f69dc36..a52aef6 100644
--- a/solr/core/src/java/org/apache/solr/response/JSONResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/JSONResponseWriter.java
@@ -24,6 +24,7 @@ import java.util.Map;
 import org.apache.solr.common.PushWriter;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.JsonTextWriter;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.request.SolrQueryRequest;
@@ -50,10 +51,10 @@ public class JSONResponseWriter implements QueryResponseWriter {
   public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
     final SolrParams params = req.getParams();
     final String wrapperFunction = params.get(JSONWriter.JSON_WRAPPER_FUNCTION);
-    final String namedListStyle = params.get(JSONWriter.JSON_NL_STYLE, JSONWriter.JSON_NL_FLAT).intern();
+    final String namedListStyle = params.get(JsonTextWriter.JSON_NL_STYLE, JsonTextWriter.JSON_NL_FLAT).intern();
 
     final JSONWriter w;
-    if (namedListStyle.equals(JSONWriter.JSON_NL_ARROFNTV)) {
+    if (namedListStyle.equals(JsonTextWriter.JSON_NL_ARROFNTV)) {
       w = new ArrayOfNameTypeValueJSONWriter(
           writer, req, rsp, wrapperFunction, namedListStyle, true);
     } else {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/JSONWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/JSONWriter.java b/solr/core/src/java/org/apache/solr/response/JSONWriter.java
index fb615cc..77aee3d 100644
--- a/solr/core/src/java/org/apache/solr/response/JSONWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/JSONWriter.java
@@ -19,30 +19,17 @@ package org.apache.solr.response;
 
 import java.io.IOException;
 import java.io.Writer;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
-import org.apache.solr.common.IteratorWriter;
-import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.SolrDocument;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.util.JsonTextWriter;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.search.ReturnFields;
 
-public class JSONWriter extends TextResponseWriter {
-  public static final String JSON_NL_MAP="map";
-  public static final String JSON_NL_FLAT="flat";
-  public static final String JSON_NL_ARROFARR="arrarr";
-  public static final String JSON_NL_ARROFMAP="arrmap";
-  public static final String JSON_NL_ARROFNTV="arrntv";
-  static final String JSON_NL_STYLE="json.nl";
+public class JSONWriter extends TextResponseWriter implements JsonTextWriter {
   static final int    JSON_NL_STYLE_COUNT = 5; // for use by JSONWriterTest
   static final String JSON_WRAPPER_FUNCTION="json.wrf";
-  private static char[] hexdigits = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
+
   final protected String namedListStyle;
   protected String wrapperFunction;
 
@@ -73,236 +60,21 @@ public class JSONWriter extends TextResponseWriter {
 
   }
 
-  protected static void unicodeEscape(Appendable out, int ch) throws IOException {
-    out.append('\\');
-    out.append('u');
-    out.append(hexdigits[(ch>>>12)     ]);
-    out.append(hexdigits[(ch>>>8) & 0xf]);
-    out.append(hexdigits[(ch>>>4) & 0xf]);
-    out.append(hexdigits[(ch)     & 0xf]);
+  @Override
+  public String getNamedListStyle() {
+    return namedListStyle;
   }
 
+
   public void writeResponse() throws IOException {
     if(wrapperFunction!=null) {
-        writer.write(wrapperFunction + "(");
+      _writeStr(wrapperFunction + "(");
     }
     writeNamedList(null, rsp.getValues());
     if(wrapperFunction!=null) {
-        writer.write(')');
-    }
-    writer.write('\n');  // ending with a newline looks much better from the command line
-  }
-
-  protected void writeKey(String fname, boolean needsEscaping) throws IOException {
-    writeStr(null, fname, needsEscaping);
-    writer.write(':');
-  }
-
-  /** Represents a NamedList directly as a JSON Object (essentially a Map)
-   * Map null to "" and name mangle any repeated keys to avoid repeats in the
-   * output.
-   */
-  protected void writeNamedListAsMapMangled(String name, NamedList val) throws IOException {
-    int sz = val.size();
-    writeMapOpener(sz);
-    incLevel();
-
-    // In JSON objects (maps) we can't have null keys or duplicates...
-    // map null to "" and append a qualifier to duplicates.
-    //
-    // a=123,a=456 will be mapped to {a=1,a__1=456}
-    // Disad: this is ambiguous since a real key could be called a__1
-    //
-    // Another possible mapping could aggregate multiple keys to an array:
-    // a=123,a=456 maps to a=[123,456]
-    // Disad: this is ambiguous with a real single value that happens to be an array
-    //
-    // Both of these mappings have ambiguities.
-    HashMap<String,Integer> repeats = new HashMap<>(4);
-
-    boolean first=true;
-    for (int i=0; i<sz; i++) {
-      String key = val.getName(i);
-      if (key==null) key="";
-
-      if (first) {
-        first=false;
-        repeats.put(key,0);
-      } else {
-        writeMapSeparator();
-
-        Integer repeatCount = repeats.get(key);
-        if (repeatCount==null) {
-          repeats.put(key,0);
-        } else {
-          String newKey = key;
-          int newCount = repeatCount;
-          do {  // avoid generated key clashing with a real key
-            newKey = key + ' ' + (++newCount);
-            repeatCount = repeats.get(newKey);
-          } while (repeatCount != null);
-
-          repeats.put(key,newCount);
-          key = newKey;
-        }
-      }
-
-      indent();
-      writeKey(key, true);
-      writeVal(key,val.getVal(i));
-    }
-
-    decLevel();
-    writeMapCloser();
-  }
-
-  /** Represents a NamedList directly as a JSON Object (essentially a Map)
-   * repeating any keys if they are repeated in the NamedList.
-   * null key is mapped to "".
-   */
-  // NamedList("a"=1,"bar"="foo",null=3,null=null) => {"a":1,"bar":"foo","":3,"":null}
-  protected void writeNamedListAsMapWithDups(String name, NamedList val) throws IOException {
-    int sz = val.size();
-    writeMapOpener(sz);
-    incLevel();
-
-    for (int i=0; i<sz; i++) {
-      if (i!=0) {
-        writeMapSeparator();
-      }
-
-      String key = val.getName(i);
-      if (key==null) key="";
-      indent();
-      writeKey(key, true);
-      writeVal(key,val.getVal(i));
-    }
-
-    decLevel();
-    writeMapCloser();
-  }
-
-  // Represents a NamedList directly as an array of JSON objects...
-  // NamedList("a"=1,"b"=2,null=3,null=null) => [{"a":1},{"b":2},3,null]
-  protected void writeNamedListAsArrMap(String name, NamedList val) throws IOException {
-    int sz = val.size();
-    indent();
-    writeArrayOpener(sz);
-    incLevel();
-
-    boolean first=true;
-    for (int i=0; i<sz; i++) {
-      String key = val.getName(i);
-
-      if (first) {
-        first=false;
-      } else {
-        writeArraySeparator();
-      }
-
-      indent();
-
-      if (key==null) {
-        writeVal(null,val.getVal(i));
-      } else {
-        writeMapOpener(1);
-        writeKey(key, true);
-        writeVal(key,val.getVal(i));
-        writeMapCloser();
-      }
-
-    }
-
-    decLevel();
-    writeArrayCloser();
-  }
-
-  // Represents a NamedList directly as an array of JSON objects...
-  // NamedList("a"=1,"b"=2,null=3,null=null) => [["a",1],["b",2],[null,3],[null,null]]
-  protected void writeNamedListAsArrArr(String name, NamedList val) throws IOException {
-    int sz = val.size();
-    indent();
-    writeArrayOpener(sz);
-    incLevel();
-
-    boolean first=true;
-    for (int i=0; i<sz; i++) {
-      String key = val.getName(i);
-
-      if (first) {
-        first=false;
-      } else {
-        writeArraySeparator();
-      }
-
-      indent();
-
-      /*** if key is null, just write value???
-      if (key==null) {
-        writeVal(null,val.getVal(i));
-      } else {
-     ***/
-
-        writeArrayOpener(1);
-        incLevel();
-        if (key==null) {
-          writeNull(null);
-        } else {
-          writeStr(null, key, true);
-        }
-        writeArraySeparator();
-        writeVal(key,val.getVal(i));
-        decLevel();
-        writeArrayCloser();
-    }
-
-    decLevel();
-    writeArrayCloser();
-  }
-
-  // Represents a NamedList directly as an array with keys/values
-  // interleaved.
-  // NamedList("a"=1,"b"=2,null=3,null=null) => ["a",1,"b",2,null,3,null,null]
-  protected void writeNamedListAsFlat(String name, NamedList val) throws IOException {
-    int sz = val.size();
-    writeArrayOpener(sz*2);
-    incLevel();
-
-    for (int i=0; i<sz; i++) {
-      if (i!=0) {
-        writeArraySeparator();
-      }
-      String key = val.getName(i);
-      indent();
-      if (key==null) {
-        writeNull(null);
-      } else {
-        writeStr(null, key, true);
-      }
-      writeArraySeparator();
-      writeVal(key, val.getVal(i));
-    }
-
-    decLevel();
-    writeArrayCloser();
-  }
-
-  @Override
-  public void writeNamedList(String name, NamedList val) throws IOException {
-    if (val instanceof SimpleOrderedMap) {
-      writeNamedListAsMapWithDups(name,val);
-    } else if (namedListStyle==JSON_NL_FLAT) {
-      writeNamedListAsFlat(name,val);
-    } else if (namedListStyle==JSON_NL_MAP){
-      writeNamedListAsMapWithDups(name,val);
-    } else if (namedListStyle==JSON_NL_ARROFARR) {
-      writeNamedListAsArrArr(name,val);
-    } else if (namedListStyle==JSON_NL_ARROFMAP) {
-      writeNamedListAsArrMap(name,val);
-    } else if (namedListStyle==JSON_NL_ARROFNTV) {
-      throw new UnsupportedOperationException(namedListStyle
-          + " namedListStyle must only be used with "+ArrayOfNameTypeValueJSONWriter.class.getSimpleName());
+      _writeChar(')');
     }
+    _writeChar('\n');  // ending with a newline looks much better from the command line
   }
 
   @Override
@@ -396,229 +168,14 @@ public class JSONWriter extends TextResponseWriter {
     writeMapCloser();
   }
 
-  public void writeMapOpener(int size) throws IOException, IllegalArgumentException {
-    writer.write('{');
-  }
-
-  public void writeMapSeparator() throws IOException {
-    writer.write(',');
-  }
-
-  public void writeMapCloser() throws IOException {
-    writer.write('}');
-  }
-
-  public void writeArrayOpener(int size) throws IOException, IllegalArgumentException {
-    writer.write('[');
-  }
-
-  public void writeArraySeparator() throws IOException {
-    writer.write(',');
-  }
-
-  public void writeArrayCloser() throws IOException {
-    writer.write(']');
-  }
-
-  @Override
-  public void writeStr(String name, String val, boolean needsEscaping) throws IOException {
-    // it might be more efficient to use a stringbuilder or write substrings
-    // if writing chars to the stream is slow.
-    if (needsEscaping) {
-
-
-     /* http://www.ietf.org/internet-drafts/draft-crockford-jsonorg-json-04.txt
-      All Unicode characters may be placed within
-      the quotation marks except for the characters which must be
-      escaped: quotation mark, reverse solidus, and the control
-      characters (U+0000 through U+001F).
-     */
-      writer.write('"');
-
-      for (int i=0; i<val.length(); i++) {
-        char ch = val.charAt(i);
-        if ((ch > '#' && ch != '\\' && ch < '\u2028') || ch == ' ') { // fast path
-          writer.write(ch);
-          continue;
-        }
-        switch(ch) {
-          case '"':
-          case '\\':
-            writer.write('\\');
-            writer.write(ch);
-            break;
-          case '\r': writer.write('\\'); writer.write('r'); break;
-          case '\n': writer.write('\\'); writer.write('n'); break;
-          case '\t': writer.write('\\'); writer.write('t'); break;
-          case '\b': writer.write('\\'); writer.write('b'); break;
-          case '\f': writer.write('\\'); writer.write('f'); break;
-          case '\u2028': // fallthrough
-          case '\u2029':
-            unicodeEscape(writer,ch);
-            break;
-          // case '/':
-          default: {
-            if (ch <= 0x1F) {
-              unicodeEscape(writer,ch);
-            } else {
-              writer.write(ch);
-            }
-          }
-        }
-      }
-
-      writer.write('"');
-    } else {
-      writer.write('"');
-      writer.write(val);
-      writer.write('"');
-    }
-  }
-
-  @Override
-  public void writeIterator(IteratorWriter val) throws IOException {
-    writeArrayOpener(-1);
-    incLevel();
-    val.writeIter(new IteratorWriter.ItemWriter() {
-      boolean first = true;
-
-      @Override
-      public IteratorWriter.ItemWriter add(Object o) throws IOException {
-        if (!first) {
-          JSONWriter.this.indent();
-          JSONWriter.this.writeArraySeparator();
-        }
-        JSONWriter.this.writeVal(null, o);
-        first = false;
-        return this;
-      }
-    });
-    decLevel();
-    writeArrayCloser();
-  }
-
-  @Override
-  public void writeMap(MapWriter val)
-      throws IOException {
-    writeMapOpener(-1);
-    incLevel();
-
-    val.writeMap(new MapWriter.EntryWriter() {
-      boolean isFirst = true;
-
-      @Override
-      public MapWriter.EntryWriter put(String k, Object v) throws IOException {
-        if (isFirst) {
-          isFirst = false;
-        } else {
-          JSONWriter.this.writeMapSeparator();
-        }
-        if (doIndent) JSONWriter.this.indent();
-        JSONWriter.this.writeKey(k, true);
-        JSONWriter.this.writeVal(k, v);
-        return this;
-      }
-    });
-    decLevel();
-    writeMapCloser();
-  }
-
-  @Override
-  public void writeMap(String name, Map val, boolean excludeOuter, boolean isFirstVal) throws IOException {
-    if (!excludeOuter) {
-      writeMapOpener(val.size());
-      incLevel();
-      isFirstVal=true;
-    }
-
-    boolean doIndent = excludeOuter || val.size() > 1;
-
-    for (Map.Entry entry : (Set<Map.Entry>)val.entrySet()) {
-      Object e = entry.getKey();
-      String k = e==null ? "" : e.toString();
-      Object v = entry.getValue();
-
-      if (isFirstVal) {
-        isFirstVal=false;
-      } else {
-        writeMapSeparator();
-      }
-
-      if (doIndent) indent();
-      writeKey(k,true);
-      writeVal(k,v);
-    }
-
-    if (!excludeOuter) {
-      decLevel();
-      writeMapCloser();
-    }
-  }
-
-  @Override
-  public void writeArray(String name, List l) throws IOException {
-    writeArrayOpener(l.size());
-    writeJsonIter(l.iterator());
-    writeArrayCloser();
-  }
-
-  @Override
-  public void writeArray(String name, Iterator val) throws IOException {
-    writeArrayOpener(-1); // no trivial way to determine array size
-    writeJsonIter(val);
-    writeArrayCloser();
-  }
-
-  private void writeJsonIter(Iterator val) throws IOException {
-    incLevel();
-    boolean first=true;
-    while( val.hasNext() ) {
-      if( !first ) indent();
-      writeVal(null, val.next());
-      if( val.hasNext() ) {
-        writeArraySeparator();
-      }
-      first=false;
-    }
-    decLevel();
-  }
-
-  //
-  // Primitive types
-  //
-  @Override
-  public void writeNull(String name) throws IOException {
-    writer.write("null");
-  }
-
-  @Override
-  public void writeInt(String name, String val) throws IOException {
-    writer.write(val);
-  }
-
-  @Override
-  public void writeLong(String name, String val) throws IOException {
-    writer.write(val);
-  }
-
-  @Override
-  public void writeBool(String name, String val) throws IOException {
-    writer.write(val);
-  }
-
-  @Override
-  public void writeFloat(String name, String val) throws IOException {
-    writer.write(val);
-  }
-
   @Override
-  public void writeDouble(String name, String val) throws IOException {
-    writer.write(val);
+  public void _writeChar(char c) throws IOException {
+    writer.write(c);
   }
 
   @Override
-  public void writeDate(String name, String val) throws IOException {
-    writeStr(name, val, false);
+  public void _writeStr(String s) throws IOException {
+    writer.write(s);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/PHPResponseWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/PHPResponseWriter.java b/solr/core/src/java/org/apache/solr/response/PHPResponseWriter.java
index a419fbb..ad12128 100644
--- a/solr/core/src/java/org/apache/solr/response/PHPResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/PHPResponseWriter.java
@@ -16,8 +16,8 @@
  */
 package org.apache.solr.response;
 
-import java.io.Writer;
 import java.io.IOException;
+import java.io.Writer;
 import java.util.List;
 
 import org.apache.solr.common.util.NamedList;
@@ -93,7 +93,7 @@ class PHPWriter extends JSONWriter {
   }
 
   @Override
-  protected void writeKey(String fname, boolean needsEscaping) throws IOException {
+  public void writeKey(String fname, boolean needsEscaping) throws IOException {
     writeStr(null, fname, needsEscaping);
     writer.write('=');
     writer.write('>');

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/PHPSerializedResponseWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/PHPSerializedResponseWriter.java b/solr/core/src/java/org/apache/solr/response/PHPSerializedResponseWriter.java
index c660aae..05208be 100644
--- a/solr/core/src/java/org/apache/solr/response/PHPSerializedResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/PHPSerializedResponseWriter.java
@@ -225,7 +225,7 @@ class PHPSerializedWriter extends JSONWriter {
   }
 
   @Override
-  protected void writeKey(String fname, boolean needsEscaping) throws IOException {
+  public void writeKey(String fname, boolean needsEscaping) throws IOException {
     writeStr(null, fname, needsEscaping);
   }
   void writeKey(int val, boolean needsEscaping) throws IOException {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/QueryResponseWriterUtil.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/QueryResponseWriterUtil.java b/solr/core/src/java/org/apache/solr/response/QueryResponseWriterUtil.java
index ed47e84..4146384 100644
--- a/solr/core/src/java/org/apache/solr/response/QueryResponseWriterUtil.java
+++ b/solr/core/src/java/org/apache/solr/response/QueryResponseWriterUtil.java
@@ -24,8 +24,8 @@ import java.io.Writer;
 import java.nio.charset.StandardCharsets;
 
 import org.apache.solr.common.util.ContentStreamBase;
+import org.apache.solr.common.util.FastWriter;
 import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.util.FastWriter;
 
 /**
  * Static utility methods relating to {@link QueryResponseWriter}s

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/RubyResponseWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/RubyResponseWriter.java b/solr/core/src/java/org/apache/solr/response/RubyResponseWriter.java
index 30e6f9f..6b73a7c 100644
--- a/solr/core/src/java/org/apache/solr/response/RubyResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/RubyResponseWriter.java
@@ -16,8 +16,8 @@
  */
 package org.apache.solr.response;
 
-import java.io.Writer;
 import java.io.IOException;
+import java.io.Writer;
 
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.request.SolrQueryRequest;
@@ -63,7 +63,7 @@ class RubyWriter extends NaNFloatWriter {
   }
 
   @Override
-  protected void writeKey(String fname, boolean needsEscaping) throws IOException {
+  public void writeKey(String fname, boolean needsEscaping) throws IOException {
     writeStr(null, fname, needsEscaping);
     writer.write('=');
     writer.write('>');

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/SmileResponseWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/SmileResponseWriter.java b/solr/core/src/java/org/apache/solr/response/SmileResponseWriter.java
index 0bd0658..0ca6fa6 100644
--- a/solr/core/src/java/org/apache/solr/response/SmileResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/SmileResponseWriter.java
@@ -67,7 +67,7 @@ public class SmileResponseWriter extends BinaryResponseWriter {
     }
 
     @Override
-    protected void writeNumber(String name, Number val) throws IOException {
+    public void writeNumber(String name, Number val) throws IOException {
       if (val instanceof Integer) {
         gen.writeNumber(val.intValue());
       } else if (val instanceof Long) {
@@ -156,7 +156,7 @@ public class SmileResponseWriter extends BinaryResponseWriter {
     }
 
     @Override
-    protected void writeKey(String fname, boolean needsEscaping) throws IOException {
+    public void writeKey(String fname, boolean needsEscaping) throws IOException {
       gen.writeFieldName(fname);
     }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/TextResponseWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/TextResponseWriter.java b/solr/core/src/java/org/apache/solr/response/TextResponseWriter.java
index 98c181a..250b37d 100644
--- a/solr/core/src/java/org/apache/solr/response/TextResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/TextResponseWriter.java
@@ -18,52 +18,29 @@ package org.apache.solr.response;
 
 import java.io.IOException;
 import java.io.Writer;
-import java.nio.file.Path;
-import java.util.Arrays;
 import java.util.Calendar;
-import java.util.Date;
 import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.IndexableField;
 import org.apache.lucene.util.BytesRef;
-import org.apache.solr.common.EnumFieldValue;
-import org.apache.solr.common.IteratorWriter;
-import org.apache.solr.common.MapSerializable;
-import org.apache.solr.common.MapWriter;
-import org.apache.solr.common.PushWriter;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.util.Base64;
-import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.FastWriter;
+import org.apache.solr.common.util.TextWriter;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.DocList;
 import org.apache.solr.search.ReturnFields;
-import org.apache.solr.util.FastWriter;
 
 /** Base class for text-oriented response writers.
  *
  *
  */
-public abstract class TextResponseWriter implements PushWriter {
+public abstract class TextResponseWriter implements TextWriter {
 
-  // indent up to 40 spaces
-  static final char[] indentChars = new char[81];
-  static {
-    Arrays.fill(indentChars,' ');
-    indentChars[0] = '\n';  // start with a newline
-  }
-
-  
   protected final FastWriter writer;
   protected final IndexSchema schema;
   protected final SolrQueryRequest req;
@@ -105,18 +82,15 @@ public abstract class TextResponseWriter implements PushWriter {
     if(writer != null) writer.flushBuffer();
   }
 
+  @Override
+  public boolean doIndent() {
+    return doIndent;
+  }
+
   /** returns the Writer that the response is being written to */
   public Writer getWriter() { return writer; }
 
 
-  public void indent() throws IOException {
-     if (doIndent) indent(level);
-  }
-
-  public void indent(int lev) throws IOException {
-    writer.write(indentChars, 0, Math.min((lev<<1)+1, indentChars.length));
-  }
-
   //
   // Functions to manipulate the current logical nesting level.
   // Any indentation will be partially based on level.
@@ -125,13 +99,12 @@ public abstract class TextResponseWriter implements PushWriter {
   public int level() { return level; }
   public int incLevel() { return ++level; }
   public int decLevel() { return --level; }
-  public void setIndent(boolean doIndent) {
+  public TextResponseWriter setIndent(boolean doIndent) {
     this.doIndent = doIndent;
+    return this;
   }
 
 
-  public abstract void writeNamedList(String name, NamedList val) throws IOException;
-
   public final void writeVal(String name, Object val) throws IOException {
 
     // if there get to be enough types, perhaps hashing on the type
@@ -141,10 +114,10 @@ public abstract class TextResponseWriter implements PushWriter {
     // go in order of most common to least common, however some of the more general types like Map belong towards the end
     if (val == null) {
       writeNull(name);
-    } else if (val instanceof String) {
-      writeStr(name, val.toString(), true);
-      // micro-optimization... using toString() avoids a cast first
-    } else if (val instanceof IndexableField) {
+      return;
+    }
+
+    if(val instanceof IndexableField) {
       IndexableField f = (IndexableField)val;
       SchemaField sf = schema.getFieldOrNull( f.name() );
       if( sf != null ) {
@@ -153,14 +126,6 @@ public abstract class TextResponseWriter implements PushWriter {
       else {
         writeStr(name, f.stringValue(), true);
       }
-    } else if (val instanceof Number) {
-      writeNumber(name, (Number) val);
-    } else if (val instanceof Boolean) {
-      writeBool(name, (Boolean) val);
-    } else if (val instanceof AtomicBoolean)  {
-      writeBool(name, ((AtomicBoolean) val).get());
-    } else if (val instanceof Date) {
-      writeDate(name, (Date) val);
     } else if (val instanceof Document) {
       SolrDocument doc = DocsStreamer.convertLuceneDocToSolrDoc((Document) val, schema, returnFields);
       writeSolrDocument(name, doc, returnFields, 0);
@@ -180,79 +145,13 @@ public abstract class TextResponseWriter implements PushWriter {
     // restricts the fields to write...?
     } else if (val instanceof SolrDocumentList) {
       writeSolrDocumentList(name, (SolrDocumentList)val, returnFields);
-    } else if (val instanceof NamedList) {
-      writeNamedList(name, (NamedList)val);
-    } else if (val instanceof Path) {
-      writeStr(name, ((Path) val).toAbsolutePath().toString(), true);
-    } else if (val instanceof IteratorWriter) {
-      writeIterator((IteratorWriter) val);
-    } else if (val instanceof MapWriter) {
-      writeMap((MapWriter) val);
-    } else if (val instanceof MapSerializable) {
-      //todo find a better way to reuse the map more efficiently
-      writeMap(name, ((MapSerializable) val).toMap(new LinkedHashMap<>()), false, true);
-    } else if (val instanceof Map) {
-      writeMap(name, (Map)val, false, true);
-    } else if (val instanceof Iterator) { // very generic; keep towards the end
-      writeArray(name, (Iterator) val);
-    } else if (val instanceof Iterable) { // very generic; keep towards the end
-      writeArray(name,((Iterable)val).iterator());
-    } else if (val instanceof Object[]) {
-      writeArray(name,(Object[])val);
-    } else if (val instanceof byte[]) {
-      byte[] arr = (byte[])val;
-      writeByteArr(name, arr, 0, arr.length);
     } else if (val instanceof BytesRef) {
       BytesRef arr = (BytesRef)val;
       writeByteArr(name, arr.bytes, arr.offset, arr.length);
-    } else if (val instanceof EnumFieldValue) {
-      writeStr(name, val.toString(), true);
-    } else if (val instanceof WriteableValue) {
-      ((WriteableValue)val).write(name, this);
     } else {
-      // default... for debugging only.  Would be nice to "assert false" ?
-      writeStr(name, val.getClass().getName() + ':' + val.toString(), true);
+      TextWriter.super.writeVal(name, val);
     }
   }
-  @Override
-  public void writeMap(MapWriter mw) throws IOException {
-    //todo
-  }
-
-  @Override
-  public void writeIterator(IteratorWriter iw) throws IOException {
-    /*todo*/
-  }
-
-  protected void writeBool(String name , Boolean val) throws IOException {
-    writeBool(name, val.toString());
-  }
-
-  protected void writeNumber(String name, Number val) throws IOException {
-    if (val instanceof Integer) {
-      writeInt(name, val.toString());
-    } else if (val instanceof Long) {
-      writeLong(name, val.toString());
-    } else if (val instanceof Float) {
-      // we pass the float instead of using toString() because
-      // it may need special formatting. same for double.
-      writeFloat(name, val.floatValue());
-    } else if (val instanceof Double) {
-      writeDouble(name, val.doubleValue());
-    } else if (val instanceof Short) {
-      writeInt(name, val.toString());
-    } else if (val instanceof Byte) {
-      writeInt(name, val.toString());
-    } else if (val instanceof AtomicInteger) {
-      writeInt(name, ((AtomicInteger) val).get());
-    } else if (val instanceof AtomicLong) {
-      writeLong(name, ((AtomicLong) val).get());
-    } else {
-      // default... for debugging only
-      writeStr(name, val.getClass().getName() + ':' + val.toString(), true);
-    }
-  }
-
   // names are passed when writing primitives like writeInt to allow many different
   // types of formats, including those where the name may come after the value (like
   // some XML formats).
@@ -287,84 +186,4 @@ public abstract class TextResponseWriter implements PushWriter {
     }
     writeEndDocumentList();
   }
-  
-  
-  public abstract void writeStr(String name, String val, boolean needsEscaping) throws IOException;
-
-  public abstract void writeMap(String name, Map val, boolean excludeOuter, boolean isFirstVal) throws IOException;
-
-  public void writeArray(String name, Object[] val) throws IOException {
-    writeArray(name, Arrays.asList(val));
-  }
-
-  public void writeArray(String name, List l) throws IOException {
-    writeArray(name, l.iterator());
-  }
-  
-  public abstract void writeArray(String name, Iterator val) throws IOException;
-
-  public abstract void writeNull(String name) throws IOException;
-
-  /** if this form of the method is called, val is the Java string form of an int */
-  public abstract void writeInt(String name, String val) throws IOException;
-
-  public void writeInt(String name, int val) throws IOException {
-    writeInt(name,Integer.toString(val));
-  }
-
-  /** if this form of the method is called, val is the Java string form of a long */
-  public abstract void writeLong(String name, String val) throws IOException;
-
-  public  void writeLong(String name, long val) throws IOException {
-    writeLong(name,Long.toString(val));
-  }
-
-  /** if this form of the method is called, val is the Java string form of a boolean */
-  public abstract void writeBool(String name, String val) throws IOException;
-
-  public void writeBool(String name, boolean val) throws IOException {
-    writeBool(name,Boolean.toString(val));
-  }
-
-  /** if this form of the method is called, val is the Java string form of a float */
-  public abstract void writeFloat(String name, String val) throws IOException;
-
-  public void writeFloat(String name, float val) throws IOException {
-    String s = Float.toString(val);
-    // If it's not a normal number, write the value as a string instead.
-    // The following test also handles NaN since comparisons are always false.
-    if (val > Float.NEGATIVE_INFINITY && val < Float.POSITIVE_INFINITY) {
-      writeFloat(name,s);
-    } else {
-      writeStr(name,s,false);
-    }
-  }
-
-
-  /** if this form of the method is called, val is the Java string form of a double */
-  public abstract void writeDouble(String name, String val) throws IOException;
-
-  public void writeDouble(String name, double val) throws IOException {
-    String s = Double.toString(val);
-    // If it's not a normal number, write the value as a string instead.
-    // The following test also handles NaN since comparisons are always false.
-    if (val > Double.NEGATIVE_INFINITY && val < Double.POSITIVE_INFINITY) {
-      writeDouble(name,s);
-    } else {
-      writeStr(name,s,false);
-    }
-  }
-
-
-  public void writeDate(String name, Date val) throws IOException {
-    writeDate(name, val.toInstant().toString());
-  }
-  
-
-  /** if this form of the method is called, val is the Solr ISO8601 based date format */
-  public abstract void writeDate(String name, String val) throws IOException;
-
-  public void writeByteArr(String name, byte[] buf, int offset, int len) throws IOException {
-    writeStr(name, Base64.byteArrayToBase64(buf, offset, len), false);
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/WriteableValue.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/WriteableValue.java b/solr/core/src/java/org/apache/solr/response/WriteableValue.java
deleted file mode 100644
index 15b9093..0000000
--- a/solr/core/src/java/org/apache/solr/response/WriteableValue.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * 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.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.response;
-
-import java.io.IOException;
-
-import org.apache.solr.common.util.JavaBinCodec.ObjectResolver;
-
-public abstract class WriteableValue implements ObjectResolver {
-  public abstract void write(String name, TextResponseWriter writer) throws IOException;
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/transform/RawValueTransformerFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/transform/RawValueTransformerFactory.java b/solr/core/src/java/org/apache/solr/response/transform/RawValueTransformerFactory.java
index aaf9861..25fe56c 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/RawValueTransformerFactory.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/RawValueTransformerFactory.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 
+import com.google.common.base.Strings;
 import org.apache.lucene.index.IndexableField;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.params.CommonParams;
@@ -27,12 +28,10 @@ import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.JavaBinCodec;
 import org.apache.solr.common.util.JavaBinCodec.ObjectResolver;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.TextWriter;
+import org.apache.solr.common.util.WriteableValue;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.QueryResponseWriter;
-import org.apache.solr.response.TextResponseWriter;
-import org.apache.solr.response.WriteableValue;
-
-import com.google.common.base.Strings;
 
 /**
  * @since solr 5.2
@@ -158,7 +157,7 @@ public class RawValueTransformerFactory extends TransformerFactory
     }
     
     @Override
-    public void write(String name, TextResponseWriter writer) throws IOException {
+    public void write(String name, TextWriter writer) throws IOException {
       String str = null;
       if(val instanceof IndexableField) { // delays holding it in memory
         str = ((IndexableField)val).stringValue();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/response/transform/WriteableGeoJSON.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/response/transform/WriteableGeoJSON.java b/solr/core/src/java/org/apache/solr/response/transform/WriteableGeoJSON.java
index 40acebf..f89f5c8 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/WriteableGeoJSON.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/WriteableGeoJSON.java
@@ -19,8 +19,8 @@ package org.apache.solr.response.transform;
 import java.io.IOException;
 
 import org.apache.solr.common.util.JavaBinCodec;
-import org.apache.solr.response.TextResponseWriter;
-import org.apache.solr.response.WriteableValue;
+import org.apache.solr.common.util.TextWriter;
+import org.apache.solr.common.util.WriteableValue;
 import org.locationtech.spatial4j.io.ShapeWriter;
 import org.locationtech.spatial4j.shape.Shape;
 
@@ -44,7 +44,7 @@ public class WriteableGeoJSON extends WriteableValue {
   }
 
   @Override
-  public void write(String name, TextResponseWriter writer) throws IOException {
+  public void write(String name, TextWriter writer) throws IOException {
     jsonWriter.write(writer.getWriter(), shape);
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/java/org/apache/solr/util/FastWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/util/FastWriter.java b/solr/core/src/java/org/apache/solr/util/FastWriter.java
deleted file mode 100644
index 7b9b687..0000000
--- a/solr/core/src/java/org/apache/solr/util/FastWriter.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * 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.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.util;
-
-import java.io.Writer;
-import java.io.IOException;
-
-/** Single threaded BufferedWriter
- *  Internal Solr use only, subject to change.
- */
-public class FastWriter extends Writer {
-  // use default BUFSIZE of BufferedWriter so if we wrap that
-  // it won't cause double buffering.
-  private static final int BUFSIZE = 8192;
-  protected final Writer sink;
-  protected char[] buf;
-  protected int pos;
-
-  public FastWriter(Writer w) {
-    this(w, new char[BUFSIZE], 0);
-  }
-
-  public FastWriter(Writer sink, char[] tempBuffer, int start) {
-    this.sink = sink;
-    this.buf = tempBuffer;
-    this.pos = start;
-  }
-
-  public static FastWriter wrap(Writer sink) {
-    return (sink instanceof FastWriter) ? (FastWriter)sink : new FastWriter(sink);
-  }
-
-  @Override
-  public void write(int c) throws IOException {
-    write((char)c); 
-  }
-
-  public void write(char c) throws IOException {
-    if (pos >= buf.length) {
-      flush(buf,0,pos);
-      pos=0;
-    }
-    buf[pos++] = c;
-  }
-
-  @Override
-  public FastWriter append(char c) throws IOException {
-    if (pos >= buf.length) {
-      flush(buf,0,pos);
-      pos=0;
-    }
-    buf[pos++] = c;
-    return this;
-  }
-
-  @Override
-  public void write(char arr[], int off, int len) throws IOException {
-    for(;;) {
-      int space = buf.length - pos;
-
-      if (len <= space) {
-        System.arraycopy(arr, off, buf, pos, len);
-        pos += len;
-        return;
-      } else if (len > buf.length) {
-        if (pos>0) {
-          flush(buf,0,pos);  // flush
-          pos=0;
-        }
-        // don't buffer, just write to sink
-        flush(arr, off, len);
-        return;
-      }
-
-      // buffer is too big to fit in the free space, but
-      // not big enough to warrant writing on its own.
-      // write whatever we can fit, then flush and iterate.
-
-      System.arraycopy(arr, off, buf, pos, space);
-      flush(buf, 0, buf.length);
-      pos = 0;
-      off += space;
-      len -= space;
-    }
-  }
-
-  @Override
-  public void write(String str, int off, int len) throws IOException {
-    for(;;) {
-      int space = buf.length - pos;
-
-      if (len <= space) {
-        str.getChars(off, off+len, buf, pos);
-        pos += len;
-        return;
-      } else if (len > buf.length) {
-        if (pos>0) {
-          flush(buf,0,pos);  // flush
-          pos=0;
-        }
-        // don't buffer, just write to sink
-        flush(str, off, len);
-        return;
-      }
-
-      // buffer is too big to fit in the free space, but
-      // not big enough to warrant writing on its own.
-      // write whatever we can fit, then flush and iterate.
-
-      str.getChars(off, off+space, buf, pos);
-      flush(buf, 0, buf.length);
-      pos = 0;
-      off += space;
-      len -= space;
-    }
-  }
-
-  @Override
-  public void flush() throws IOException {
-    flush(buf, 0, pos);
-    pos=0;
-    if (sink != null) sink.flush();
-  }
-
-  public void flush(char[] buf, int offset, int len) throws IOException {
-    sink.write(buf, offset, len);
-  }
-
-  public void flush(String str, int offset, int len) throws IOException {
-    sink.write(str, offset, len);
-  }
-
-  @Override
-  public void close() throws IOException {
-    flush();
-    if (sink != null) sink.close();
-  }
-
-  public void flushBuffer() throws IOException {
-    flush(buf, 0, pos);
-    pos=0;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/test/org/apache/solr/response/JSONWriterTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/response/JSONWriterTest.java b/solr/core/src/test/org/apache/solr/response/JSONWriterTest.java
index 68cebd2..8a6cb71 100644
--- a/solr/core/src/test/org/apache/solr/response/JSONWriterTest.java
+++ b/solr/core/src/test/org/apache/solr/response/JSONWriterTest.java
@@ -30,9 +30,10 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.JsonTextWriter;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.search.ReturnFields;
 import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.search.ReturnFields;
 import org.apache.solr.search.SolrReturnFields;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -80,11 +81,11 @@ public class JSONWriterTest extends SolrTestCaseJ4 {
   @Test
   public void testJSON() throws IOException {
     final String[] namedListStyles = new String[] {
-        JSONWriter.JSON_NL_FLAT,
-        JSONWriter.JSON_NL_MAP,
-        JSONWriter.JSON_NL_ARROFARR,
-        JSONWriter.JSON_NL_ARROFMAP,
-        JSONWriter.JSON_NL_ARROFNTV,
+        JsonTextWriter.JSON_NL_FLAT,
+        JsonTextWriter.JSON_NL_MAP,
+        JsonTextWriter.JSON_NL_ARROFARR,
+        JsonTextWriter.JSON_NL_ARROFMAP,
+        JsonTextWriter.JSON_NL_ARROFNTV,
     };
     for (final String namedListStyle : namedListStyles) {
       implTestJSON(namedListStyle);
@@ -202,17 +203,23 @@ public class JSONWriterTest extends SolrTestCaseJ4 {
     methodsExpectedNotOverriden.add("writeMapOpener");
     methodsExpectedNotOverriden.add("writeMapSeparator");
     methodsExpectedNotOverriden.add("writeMapCloser");
-    methodsExpectedNotOverriden.add("public void org.apache.solr.response.JSONWriter.writeArray(java.lang.String,java.util.List) throws java.io.IOException");
+    methodsExpectedNotOverriden.add("public default void org.apache.solr.common.util.JsonTextWriter.writeArray(java.lang.String,java.util.List) throws java.io.IOException");
     methodsExpectedNotOverriden.add("writeArrayOpener");
     methodsExpectedNotOverriden.add("writeArraySeparator");
     methodsExpectedNotOverriden.add("writeArrayCloser");
-    methodsExpectedNotOverriden.add("public void org.apache.solr.response.JSONWriter.writeMap(org.apache.solr.common.MapWriter) throws java.io.IOException");
-    methodsExpectedNotOverriden.add("public void org.apache.solr.response.JSONWriter.writeIterator(org.apache.solr.common.IteratorWriter) throws java.io.IOException");
+    methodsExpectedNotOverriden.add("public default void org.apache.solr.common.util.JsonTextWriter.writeMap(org.apache.solr.common.MapWriter) throws java.io.IOException");
+    methodsExpectedNotOverriden.add("public default void org.apache.solr.common.util.JsonTextWriter.writeIterator(org.apache.solr.common.IteratorWriter) throws java.io.IOException");
+    methodsExpectedNotOverriden.add("public default void org.apache.solr.common.util.JsonTextWriter.writeJsonIter(java.util.Iterator) throws java.io.IOException");
 
     final Class<?> subClass = ArrayOfNameTypeValueJSONWriter.class;
     final Class<?> superClass = subClass.getSuperclass();
 
-    for (final Method superClassMethod : superClass.getDeclaredMethods()) {
+    List<Method> allSuperClassMethods = new ArrayList<>();
+    for (Method method : superClass.getDeclaredMethods()) allSuperClassMethods.add(method);
+    for (Method method : JsonTextWriter.class.getDeclaredMethods()) allSuperClassMethods.add(method);
+
+
+    for (final Method superClassMethod : allSuperClassMethods) {
       final String methodName = superClassMethod.getName();
       final String methodFullName = superClassMethod.toString();
       if (!methodName.startsWith("write")) continue;
@@ -226,10 +233,7 @@ public class JSONWriterTest extends SolrTestCaseJ4 {
           && !methodsExpectedNotOverriden.contains(methodFullName);
 
       try {
-        final Method subClassMethod = subClass.getDeclaredMethod(
-            superClassMethod.getName(),
-            superClassMethod.getParameterTypes());
-
+        final Method subClassMethod = getDeclaredMethodInClasses(superClassMethod, subClass);
         if (expectOverriden) {
           assertEquals("getReturnType() difference",
               superClassMethod.getReturnType(),
@@ -260,9 +264,7 @@ public class JSONWriterTest extends SolrTestCaseJ4 {
       // only own private method of its own
       if (subClassMethod.getName().equals("ifNeededWriteTypeAndValueKey")) continue;
       try {
-        final Method superClassMethod = superClass.getDeclaredMethod(
-            subClassMethod.getName(),
-            subClassMethod.getParameterTypes());
+        final Method superClassMethod = getDeclaredMethodInClasses( subClassMethod,superClass, JsonTextWriter.class);
 
           assertEquals("getReturnType() difference",
               subClassMethod.getReturnType(),
@@ -272,7 +274,22 @@ public class JSONWriterTest extends SolrTestCaseJ4 {
       }
     }
   }
-  
+
+  private Method getDeclaredMethodInClasses(Method subClassMethod, Class<?>... classes) throws NoSuchMethodException {
+    for (int i = 0; i < classes.length; i++) {
+      Class<?> klass = classes[i];
+      try {
+        return klass.getDeclaredMethod(
+            subClassMethod.getName(),
+            subClassMethod.getParameterTypes());
+      } catch (NoSuchMethodException e) {
+        if(i==classes.length-1) throw e;
+      }
+    }
+    throw new  NoSuchMethodException(subClassMethod.toString());
+
+  }
+
   @Test
   public void testConstantsUnchanged() {
     assertEquals("json.nl", JSONWriter.JSON_NL_STYLE);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/core/src/test/org/apache/solr/util/TestFastWriter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/util/TestFastWriter.java b/solr/core/src/test/org/apache/solr/util/TestFastWriter.java
deleted file mode 100644
index 5857666..0000000
--- a/solr/core/src/test/org/apache/solr/util/TestFastWriter.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * 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.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.solr.util;
-
-import org.apache.lucene.util.LuceneTestCase;
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Random;
-
-
-class MemWriter extends FastWriter {
-  public List<char[]> buffers = new LinkedList<>();
-
-  Random r;
-  public MemWriter(char[] tempBuffer, Random r) {
-    super(null, tempBuffer, 0);
-    this.r = r;
-  }
-
-  @Override
-  public void flush(char[] arr, int offset, int len) throws IOException {
-    if (arr == buf && offset==0 && len==buf.length) {
-      buffers.add(buf);  // steal the buffer
-      buf = new char[r.nextInt(9000)+1];
-    } else if (len > 0) {
-      char[] newBuf = new char[len];
-      System.arraycopy(arr, offset, newBuf, 0, len);
-      buffers.add(newBuf);
-    }
-  }
-
-  @Override
-  public void flush(String str, int offset, int len) throws IOException {
-    if (len == 0) return;
-    buffers.add( str.substring(offset, offset+len).toCharArray() );
-  }
-}
-
-
-
-public class TestFastWriter extends LuceneTestCase {
-
-  Random rand;
-  char[] arr;
-  String s;
-
-  public void testRandomWrites() throws Exception {
-    rand = random();
-
-    arr = new char[20000];
-    for (int i=0; i<arr.length; i++) {
-      arr[i] = (char)rand.nextInt();
-    }
-    s = new String(arr);
-
-    for (int i=0; i<1000; i++) {
-      doRandomWrites();
-    }
-  }
-
-
-  public void doRandomWrites() throws Exception {
-    int bufSize = ( rand.nextBoolean() ? rand.nextInt(10) : rand.nextInt(20000) )+1;
-    MemWriter out = new MemWriter(new char[bufSize], rand);
-
-    int hash = 0;
-    long written = 0;
-    int iter = rand.nextInt(20)+1;
-    for (int i=0; i<iter; i++) {
-      int which = rand.nextInt(3);
-
-
-      int off = rand.nextInt(arr.length);
-      int len = off < arr.length ? rand.nextInt(arr.length - off) : 0;
-
-
-
-      if (which == 0) {
-        out.write(arr, off, len);
-      } else if (which == 1) {
-        out.write(s, off, len);
-      } else {
-        len = 1;
-        out.write(arr[off]);
-      }
-
-      hash = incHash(hash, arr, off, len);
-      written += len;
-    }
-
-    out.close();
-
-    int hash2 = 0;
-    for (char[] buffer : out.buffers) {
-      hash2 = incHash(hash2, buffer, 0, buffer.length);
-    }
-
-    assertEquals(hash, hash2);
-  }
-
-
-  public int incHash(int hash, char[] arr, int off, int len) {
-    for (int i=off; i<off+len; i++) {
-      hash = hash * 31 + arr[i];
-    }
-    return hash;
-  }
-}
-

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/solrj/src/java/org/apache/solr/common/util/FastWriter.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/FastWriter.java b/solr/solrj/src/java/org/apache/solr/common/util/FastWriter.java
new file mode 100644
index 0000000..2575624
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/common/util/FastWriter.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.common.util;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/** Single threaded BufferedWriter
+ *  Internal Solr use only, subject to change.
+ */
+public class FastWriter extends Writer {
+  // use default BUFSIZE of BufferedWriter so if we wrap that
+  // it won't cause double buffering.
+  private static final int BUFSIZE = 8192;
+  protected final Writer sink;
+  protected char[] buf;
+  protected int pos;
+
+  public FastWriter(Writer w) {
+    this(w, new char[BUFSIZE], 0);
+  }
+
+  public FastWriter(Writer sink, char[] tempBuffer, int start) {
+    this.sink = sink;
+    this.buf = tempBuffer;
+    this.pos = start;
+  }
+
+  public static FastWriter wrap(Writer sink) {
+    return (sink instanceof FastWriter) ? (FastWriter)sink : new FastWriter(sink);
+  }
+
+  @Override
+  public void write(int c) throws IOException {
+    write((char)c); 
+  }
+
+  public void write(char c) throws IOException {
+    if (pos >= buf.length) {
+      flush(buf,0,pos);
+      pos=0;
+    }
+    buf[pos++] = c;
+  }
+
+  @Override
+  public FastWriter append(char c) throws IOException {
+    if (pos >= buf.length) {
+      flush(buf,0,pos);
+      pos=0;
+    }
+    buf[pos++] = c;
+    return this;
+  }
+
+  @Override
+  public void write(char arr[], int off, int len) throws IOException {
+    for(;;) {
+      int space = buf.length - pos;
+
+      if (len <= space) {
+        System.arraycopy(arr, off, buf, pos, len);
+        pos += len;
+        return;
+      } else if (len > buf.length) {
+        if (pos>0) {
+          flush(buf,0,pos);  // flush
+          pos=0;
+        }
+        // don't buffer, just write to sink
+        flush(arr, off, len);
+        return;
+      }
+
+      // buffer is too big to fit in the free space, but
+      // not big enough to warrant writing on its own.
+      // write whatever we can fit, then flush and iterate.
+
+      System.arraycopy(arr, off, buf, pos, space);
+      flush(buf, 0, buf.length);
+      pos = 0;
+      off += space;
+      len -= space;
+    }
+  }
+
+  @Override
+  public void write(String str, int off, int len) throws IOException {
+    for(;;) {
+      int space = buf.length - pos;
+
+      if (len <= space) {
+        str.getChars(off, off+len, buf, pos);
+        pos += len;
+        return;
+      } else if (len > buf.length) {
+        if (pos>0) {
+          flush(buf,0,pos);  // flush
+          pos=0;
+        }
+        // don't buffer, just write to sink
+        flush(str, off, len);
+        return;
+      }
+
+      // buffer is too big to fit in the free space, but
+      // not big enough to warrant writing on its own.
+      // write whatever we can fit, then flush and iterate.
+
+      str.getChars(off, off+space, buf, pos);
+      flush(buf, 0, buf.length);
+      pos = 0;
+      off += space;
+      len -= space;
+    }
+  }
+
+  @Override
+  public void flush() throws IOException {
+    flush(buf, 0, pos);
+    pos=0;
+    if (sink != null) sink.flush();
+  }
+
+  public void flush(char[] buf, int offset, int len) throws IOException {
+    sink.write(buf, offset, len);
+  }
+
+  public void flush(String str, int offset, int len) throws IOException {
+    sink.write(str, offset, len);
+  }
+
+  @Override
+  public void close() throws IOException {
+    flush();
+    if (sink != null) sink.close();
+  }
+
+  public void flushBuffer() throws IOException {
+    flush(buf, 0, pos);
+    pos=0;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/solrj/src/java/org/apache/solr/common/util/JsonTextWriter.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/JsonTextWriter.java b/solr/solrj/src/java/org/apache/solr/common/util/JsonTextWriter.java
new file mode 100644
index 0000000..e176bd1
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/common/util/JsonTextWriter.java
@@ -0,0 +1,499 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.common.util;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.solr.common.IteratorWriter;
+import org.apache.solr.common.MapWriter;
+
+public interface JsonTextWriter extends TextWriter {
+  char[] hexdigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+  String JSON_NL_MAP = "map";
+  String JSON_NL_FLAT = "flat";
+  String JSON_NL_ARROFARR = "arrarr";
+  String JSON_NL_ARROFMAP = "arrmap";
+  String JSON_NL_ARROFNTV = "arrntv";
+  String JSON_NL_STYLE = "json.nl";
+
+  String getNamedListStyle();
+
+  void _writeChar(char c) throws IOException;
+
+  void _writeStr(String s) throws IOException;
+
+  default void writeMapOpener(int size) throws IOException, IllegalArgumentException {
+    _writeChar('{');
+  }
+
+  default void writeMapSeparator() throws IOException {
+    _writeChar(',');
+  }
+
+  default void writeMapCloser() throws IOException {
+    _writeChar('}');
+  }
+
+  default void writeArrayOpener(int size) throws IOException, IllegalArgumentException {
+    _writeChar('[');
+  }
+
+  default void writeArraySeparator() throws IOException {
+    _writeChar(',');
+  }
+
+  default void writeArrayCloser() throws IOException {
+    _writeChar(']');
+  }
+
+  default void writeStr(String name, String val, boolean needsEscaping) throws IOException {
+    // it might be more efficient to use a stringbuilder or write substrings
+    // if writing chars to the stream is slow.
+    if (needsEscaping) {
+
+
+     /* http://www.ietf.org/internet-drafts/draft-crockford-jsonorg-json-04.txt
+      All Unicode characters may be placed within
+      the quotation marks except for the characters which must be
+      escaped: quotation mark, reverse solidus, and the control
+      characters (U+0000 through U+001F).
+     */
+      _writeChar('"');
+
+      for (int i = 0; i < val.length(); i++) {
+        char ch = val.charAt(i);
+        if ((ch > '#' && ch != '\\' && ch < '\u2028') || ch == ' ') { // fast path
+          _writeChar(ch);
+          continue;
+        }
+        switch (ch) {
+          case '"':
+          case '\\':
+            _writeChar('\\');
+            _writeChar(ch);
+            break;
+          case '\r':
+            _writeChar('\\');
+            _writeChar('r');
+            break;
+          case '\n':
+            _writeChar('\\');
+            _writeChar('n');
+            break;
+          case '\t':
+            _writeChar('\\');
+            _writeChar('t');
+            break;
+          case '\b':
+            _writeChar('\\');
+            _writeChar('b');
+            break;
+          case '\f':
+            _writeChar('\\');
+            _writeChar('f');
+            break;
+          case '\u2028': // fallthrough
+          case '\u2029':
+            unicodeEscape(getWriter(), ch);
+            break;
+          // case '/':
+          default: {
+            if (ch <= 0x1F) {
+              unicodeEscape(getWriter(), ch);
+            } else {
+              _writeChar(ch);
+            }
+          }
+        }
+      }
+
+      _writeChar('"');
+    } else {
+      _writeChar('"');
+      _writeStr(val);
+      _writeChar('"');
+    }
+  }
+
+  default void writeIterator(IteratorWriter val) throws IOException {
+    writeArrayOpener(-1);
+    incLevel();
+    val.writeIter(new IteratorWriter.ItemWriter() {
+      boolean first = true;
+
+      @Override
+      public IteratorWriter.ItemWriter add(Object o) throws IOException {
+        if (!first) {
+          JsonTextWriter.this.indent();
+          JsonTextWriter.this.writeArraySeparator();
+        }
+        JsonTextWriter.this.writeVal(null, o);
+        first = false;
+        return this;
+      }
+    });
+    decLevel();
+    writeArrayCloser();
+  }
+
+  default void writeMap(MapWriter val)
+      throws IOException {
+    writeMapOpener(-1);
+    incLevel();
+
+    val.writeMap(new MapWriter.EntryWriter() {
+      boolean isFirst = true;
+
+      @Override
+      public MapWriter.EntryWriter put(String k, Object v) throws IOException {
+        if (isFirst) {
+          isFirst = false;
+        } else {
+          JsonTextWriter.this.writeMapSeparator();
+        }
+        JsonTextWriter.this.indent();
+        JsonTextWriter.this.writeKey(k, true);
+        writeVal(k, v);
+        return this;
+      }
+    });
+    decLevel();
+    writeMapCloser();
+  }
+
+  default void writeKey(String fname, boolean needsEscaping) throws IOException {
+    writeStr(null, fname, needsEscaping);
+    _writeChar(':');
+  }
+
+  default void writeJsonIter(Iterator val) throws IOException {
+    incLevel();
+    boolean first = true;
+    while (val.hasNext()) {
+      if (!first) indent();
+      writeVal(null, val.next());
+      if (val.hasNext()) {
+        writeArraySeparator();
+      }
+      first = false;
+    }
+    decLevel();
+  }
+
+  //
+  // Primitive types
+  //
+  default void writeNull(String name) throws IOException {
+    _writeStr("null");
+  }
+
+
+  default void writeInt(String name, String val) throws IOException {
+    _writeStr(val);
+  }
+
+  default void writeLong(String name, String val) throws IOException {
+    _writeStr(val);
+  }
+
+  default void writeBool(String name, String val) throws IOException {
+    _writeStr(val);
+  }
+
+  default void writeFloat(String name, String val) throws IOException {
+    _writeStr(val);
+  }
+
+  default void writeDouble(String name, String val) throws IOException {
+    _writeStr(val);
+  }
+
+  default void writeDate(String name, String val) throws IOException {
+    writeStr(name, val, false);
+  }
+
+
+  default void writeMap(String name, Map val, boolean excludeOuter, boolean isFirstVal) throws IOException {
+    if (!excludeOuter) {
+      writeMapOpener(val.size());
+      incLevel();
+      isFirstVal = true;
+    }
+
+    boolean doIndent = excludeOuter || val.size() > 1;
+
+    for (Map.Entry entry : (Set<Map.Entry>) val.entrySet()) {
+      Object e = entry.getKey();
+      String k = e == null ? "" : e.toString();
+      Object v = entry.getValue();
+
+      if (isFirstVal) {
+        isFirstVal = false;
+      } else {
+        writeMapSeparator();
+      }
+
+      if (doIndent) indent();
+      writeKey(k, true);
+      writeVal(k, v);
+    }
+
+    if (!excludeOuter) {
+      decLevel();
+      writeMapCloser();
+    }
+  }
+
+
+  default void writeArray(String name, List l) throws IOException {
+    writeArrayOpener(l.size());
+    writeJsonIter(l.iterator());
+    writeArrayCloser();
+  }
+
+  default void writeArray(String name, Iterator val) throws IOException {
+    writeArrayOpener(-1); // no trivial way to determine array size
+    writeJsonIter(val);
+    writeArrayCloser();
+  }
+
+  default void unicodeEscape(Appendable out, int ch) throws IOException {
+    out.append('\\');
+    out.append('u');
+    out.append(hexdigits[(ch >>> 12)]);
+    out.append(hexdigits[(ch >>> 8) & 0xf]);
+    out.append(hexdigits[(ch >>> 4) & 0xf]);
+    out.append(hexdigits[(ch) & 0xf]);
+  }
+
+  default void writeNamedList(String name, NamedList val) throws IOException {
+    String namedListStyle = getNamedListStyle();
+    if (val instanceof SimpleOrderedMap) {
+      writeNamedListAsMapWithDups(name, val);
+    } else if (namedListStyle == JSON_NL_FLAT) {
+      writeNamedListAsFlat(name, val);
+    } else if (namedListStyle == JSON_NL_MAP) {
+      writeNamedListAsMapWithDups(name, val);
+    } else if (namedListStyle == JSON_NL_ARROFARR) {
+      writeNamedListAsArrArr(name, val);
+    } else if (namedListStyle == JSON_NL_ARROFMAP) {
+      writeNamedListAsArrMap(name, val);
+    } else if (namedListStyle == JSON_NL_ARROFNTV) {
+      throw new UnsupportedOperationException(namedListStyle
+          + " namedListStyle must only be used with ArrayOfNameTypeValueJSONWriter");
+    }
+  }
+
+  /**
+   * Represents a NamedList directly as a JSON Object (essentially a Map)
+   * Map null to "" and name mangle any repeated keys to avoid repeats in the
+   * output.
+   */
+  default void writeNamedListAsMapMangled(String name, NamedList val) throws IOException {
+    int sz = val.size();
+    writeMapOpener(sz);
+    incLevel();
+
+    // In JSON objects (maps) we can't have null keys or duplicates...
+    // map null to "" and append a qualifier to duplicates.
+    //
+    // a=123,a=456 will be mapped to {a=1,a__1=456}
+    // Disad: this is ambiguous since a real key could be called a__1
+    //
+    // Another possible mapping could aggregate multiple keys to an array:
+    // a=123,a=456 maps to a=[123,456]
+    // Disad: this is ambiguous with a real single value that happens to be an array
+    //
+    // Both of these mappings have ambiguities.
+    HashMap<String, Integer> repeats = new HashMap<>(4);
+
+    boolean first = true;
+    for (int i = 0; i < sz; i++) {
+      String key = val.getName(i);
+      if (key == null) key = "";
+
+      if (first) {
+        first = false;
+        repeats.put(key, 0);
+      } else {
+        writeMapSeparator();
+
+        Integer repeatCount = repeats.get(key);
+        if (repeatCount == null) {
+          repeats.put(key, 0);
+        } else {
+          String newKey = key;
+          int newCount = repeatCount;
+          do {  // avoid generated key clashing with a real key
+            newKey = key + ' ' + (++newCount);
+            repeatCount = repeats.get(newKey);
+          } while (repeatCount != null);
+
+          repeats.put(key, newCount);
+          key = newKey;
+        }
+      }
+
+      indent();
+      writeKey(key, true);
+      writeVal(key, val.getVal(i));
+    }
+
+    decLevel();
+    writeMapCloser();
+  }
+
+  /**
+   * Represents a NamedList directly as a JSON Object (essentially a Map)
+   * repeating any keys if they are repeated in the NamedList.
+   * null key is mapped to "".
+   */
+  // NamedList("a"=1,"bar"="foo",null=3,null=null) => {"a":1,"bar":"foo","":3,"":null}
+  default void writeNamedListAsMapWithDups(String name, NamedList val) throws IOException {
+    int sz = val.size();
+    writeMapOpener(sz);
+    incLevel();
+
+    for (int i = 0; i < sz; i++) {
+      if (i != 0) {
+        writeMapSeparator();
+      }
+
+      String key = val.getName(i);
+      if (key == null) key = "";
+      indent();
+      writeKey(key, true);
+      writeVal(key, val.getVal(i));
+    }
+
+    decLevel();
+    writeMapCloser();
+  }
+
+  // Represents a NamedList directly as an array of JSON objects...
+  // NamedList("a"=1,"b"=2,null=3,null=null) => [{"a":1},{"b":2},3,null]
+  default void writeNamedListAsArrMap(String name, NamedList val) throws IOException {
+    int sz = val.size();
+    indent();
+    writeArrayOpener(sz);
+    incLevel();
+
+    boolean first = true;
+    for (int i = 0; i < sz; i++) {
+      String key = val.getName(i);
+
+      if (first) {
+        first = false;
+      } else {
+        writeArraySeparator();
+      }
+
+      indent();
+
+      if (key == null) {
+        writeVal(null, val.getVal(i));
+      } else {
+        writeMapOpener(1);
+        writeKey(key, true);
+        writeVal(key, val.getVal(i));
+        writeMapCloser();
+      }
+
+    }
+
+    decLevel();
+    writeArrayCloser();
+  }
+
+  // Represents a NamedList directly as an array of JSON objects...
+  // NamedList("a"=1,"b"=2,null=3,null=null) => [["a",1],["b",2],[null,3],[null,null]]
+  default void writeNamedListAsArrArr(String name, NamedList val) throws IOException {
+    int sz = val.size();
+    indent();
+    writeArrayOpener(sz);
+    incLevel();
+
+    boolean first = true;
+    for (int i = 0; i < sz; i++) {
+      String key = val.getName(i);
+
+      if (first) {
+        first = false;
+      } else {
+        writeArraySeparator();
+      }
+
+      indent();
+
+      /*** if key is null, just write value???
+       if (key==null) {
+       writeVal(null,val.getVal(i));
+       } else {
+       ***/
+
+      writeArrayOpener(1);
+      incLevel();
+      if (key == null) {
+        writeNull(null);
+      } else {
+        writeStr(null, key, true);
+      }
+      writeArraySeparator();
+      writeVal(key, val.getVal(i));
+      decLevel();
+      writeArrayCloser();
+    }
+
+    decLevel();
+    writeArrayCloser();
+  }
+
+  // Represents a NamedList directly as an array with keys/values
+  // interleaved.
+  // NamedList("a"=1,"b"=2,null=3,null=null) => ["a",1,"b",2,null,3,null,null]
+  default void writeNamedListAsFlat(String name, NamedList val) throws IOException {
+    int sz = val.size();
+    writeArrayOpener(sz * 2);
+    incLevel();
+
+    for (int i = 0; i < sz; i++) {
+      if (i != 0) {
+        writeArraySeparator();
+      }
+      String key = val.getName(i);
+      indent();
+      if (key == null) {
+        writeNull(null);
+      } else {
+        writeStr(null, key, true);
+      }
+      writeArraySeparator();
+      writeVal(key, val.getVal(i));
+    }
+
+    decLevel();
+    writeArrayCloser();
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/solrj/src/java/org/apache/solr/common/util/SolrJSONWriter.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/SolrJSONWriter.java b/solr/solrj/src/java/org/apache/solr/common/util/SolrJSONWriter.java
new file mode 100644
index 0000000..5eeefd4
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/common/util/SolrJSONWriter.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.common.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+
+/**
+ * Use this to serialize an object into Json. This only supports standard Objects
+ * and not the server-side Objects
+ */
+public class SolrJSONWriter implements JsonTextWriter {
+  // indent up to 40 spaces
+  static final char[] indentChars = new char[81];
+
+  static {
+    Arrays.fill(indentChars, ' ');
+    indentChars[0] = '\n';  // start with a newline
+  }
+
+  final protected String namedListStyle;
+  final FastWriter writer;
+  protected int level;
+  protected boolean doIndent;
+
+  public SolrJSONWriter(Writer writer) {
+    this(writer, JSON_NL_MAP);
+  }
+
+  public SolrJSONWriter(Writer writer, String namedListStyle) {
+    this.writer = writer == null ? null : FastWriter.wrap(writer);
+    this.namedListStyle = namedListStyle;
+  }
+
+  public SolrJSONWriter writeObj(Object o) throws IOException {
+    writeVal(null, o);
+    return this;
+  }
+
+  /**
+   * done with all writing
+   */
+  public void close() throws IOException {
+    if (writer != null) writer.flushBuffer();
+  }
+
+
+  @Override
+  public String getNamedListStyle() {
+    return namedListStyle;
+  }
+
+  @Override
+  public void _writeChar(char c) throws IOException {
+    writer.write(c);
+  }
+
+  @Override
+  public void _writeStr(String s) throws IOException {
+    writer.write(s);
+  }
+
+
+  public void setLevel(int level) {
+    this.level = level;
+  }
+
+  public int level() {
+    return level;
+  }
+
+  @Override
+  public int incLevel() {
+    return ++level;
+  }
+
+  @Override
+  public int decLevel() {
+    return --level;
+  }
+
+  @Override
+  public SolrJSONWriter setIndent(boolean doIndent) {
+    this.doIndent = doIndent;
+    return this;
+  }
+
+  @Override
+  public boolean doIndent() {
+    return doIndent;
+  }
+
+  @Override
+  public Writer getWriter() {
+    return writer;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/fef87d71/solr/solrj/src/java/org/apache/solr/common/util/TextWriter.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/TextWriter.java b/solr/solrj/src/java/org/apache/solr/common/util/TextWriter.java
new file mode 100644
index 0000000..b8dbeb2
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/common/util/TextWriter.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.common.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.solr.common.EnumFieldValue;
+import org.apache.solr.common.IteratorWriter;
+import org.apache.solr.common.MapSerializable;
+import org.apache.solr.common.MapWriter;
+import org.apache.solr.common.PushWriter;
+
+//Base interface for all text based writers
+public interface TextWriter extends PushWriter {
+
+  default void writeVal(String name, Object val) throws IOException {
+
+    // if there get to be enough types, perhaps hashing on the type
+    // to get a handler might be faster (but types must be exact to do that...)
+    //    (see a patch on LUCENE-3041 for inspiration)
+
+    // go in order of most common to least common, however some of the more general types like Map belong towards the end
+    if (val == null) {
+      writeNull(name);
+    } else if (val instanceof String) {
+      writeStr(name, val.toString(), true);
+      // micro-optimization... using toString() avoids a cast first
+    } else if (val instanceof Number) {
+      writeNumber(name, (Number) val);
+    } else if (val instanceof Boolean) {
+      writeBool(name, (Boolean) val);
+    } else if (val instanceof AtomicBoolean)  {
+      writeBool(name, ((AtomicBoolean) val).get());
+    } else if (val instanceof Date) {
+      writeDate(name, (Date) val);
+    } else if (val instanceof NamedList) {
+      writeNamedList(name, (NamedList)val);
+    } else if (val instanceof Path) {
+      writeStr(name, ((Path) val).toAbsolutePath().toString(), true);
+    } else if (val instanceof IteratorWriter) {
+      writeIterator((IteratorWriter) val);
+    } else if (val instanceof MapWriter) {
+      writeMap((MapWriter) val);
+    } else if (val instanceof MapSerializable) {
+      //todo find a better way to reuse the map more efficiently
+      writeMap(name, ((MapSerializable) val).toMap(new LinkedHashMap<>()), false, true);
+    } else if (val instanceof Map) {
+      writeMap(name, (Map)val, false, true);
+    } else if (val instanceof Iterator) { // very generic; keep towards the end
+      writeArray(name, (Iterator) val);
+    } else if (val instanceof Iterable) { // very generic; keep towards the end
+      writeArray(name,((Iterable)val).iterator());
+    } else if (val instanceof Object[]) {
+      writeArray(name,(Object[])val);
+    } else if (val instanceof byte[]) {
+      byte[] arr = (byte[])val;
+      writeByteArr(name, arr, 0, arr.length);
+    } else if (val instanceof EnumFieldValue) {
+      writeStr(name, val.toString(), true);
+    } else if (val instanceof WriteableValue) {
+      ((WriteableValue)val).write(name, this);
+    } else {
+      // default... for debugging only.  Would be nice to "assert false" ?
+      writeStr(name, val.getClass().getName() + ':' + val.toString(), true);
+    }
+  }
+
+  void writeStr(String name, String val, boolean needsEscaping) throws IOException;
+
+  void writeMap(String name, Map val, boolean excludeOuter, boolean isFirstVal) throws IOException;
+
+  void writeArray(String name, Iterator val) throws IOException;
+
+  void writeNull(String name) throws IOException;
+
+
+  /** if this form of the method is called, val is the Java string form of an int */
+  void writeInt(String name, String val) throws IOException;
+
+
+  /** if this form of the method is called, val is the Java string form of a long */
+  void writeLong(String name, String val) throws IOException;
+
+  /** if this form of the method is called, val is the Java string form of a boolean */
+  void writeBool(String name, String val) throws IOException;
+
+  /** if this form of the method is called, val is the Java string form of a float */
+  void writeFloat(String name, String val) throws IOException;
+
+
+  /** if this form of the method is called, val is the Java string form of a double */
+  void writeDouble(String name, String val) throws IOException;
+
+
+  /** if this form of the method is called, val is the Solr ISO8601 based date format */
+  void writeDate(String name, String val) throws IOException;
+
+  void writeNamedList(String name, NamedList val) throws IOException;
+
+  Writer getWriter();
+
+  default void writeNumber(String name, Number val) throws IOException {
+    if (val instanceof Integer) {
+      writeInt(name, val.toString());
+    } else if (val instanceof Long) {
+      writeLong(name, val.toString());
+    } else if (val instanceof Float) {
+      // we pass the float instead of using toString() because
+      // it may need special formatting. same for double.
+      writeFloat(name, val.floatValue());
+    } else if (val instanceof Double) {
+      writeDouble(name, val.doubleValue());
+    } else if (val instanceof Short) {
+      writeInt(name, val.toString());
+    } else if (val instanceof Byte) {
+      writeInt(name, val.toString());
+    } else if (val instanceof AtomicInteger) {
+      writeInt(name, ((AtomicInteger) val).get());
+    } else if (val instanceof AtomicLong) {
+      writeLong(name, ((AtomicLong) val).get());
+    } else {
+      // default... for debugging only
+      writeStr(name, val.getClass().getName() + ':' + val.toString(), true);
+    }
+  }
+
+  default void writeArray(String name, Object[] val) throws IOException {
+    writeArray(name, Arrays.asList(val));
+  }
+
+  default void writeArray(String name, List l) throws IOException {
+    writeArray(name, l.iterator());
+  }
+
+
+  default void writeDate(String name, Date val) throws IOException {
+    writeDate(name, val.toInstant().toString());
+  }
+
+  default void writeByteArr(String name, byte[] buf, int offset, int len) throws IOException {
+    writeStr(name, Base64.byteArrayToBase64(buf, offset, len), false);
+  }
+
+  default void writeInt(String name, int val) throws IOException {
+    writeInt(name,Integer.toString(val));
+  }
+
+  default void writeLong(String name, long val) throws IOException {
+    writeLong(name,Long.toString(val));
+  }
+
+
+  default void writeBool(String name, boolean val) throws IOException {
+    writeBool(name,Boolean.toString(val));
+  }
+
+
+  default void writeFloat(String name, float val) throws IOException {
+    String s = Float.toString(val);
+    // If it's not a normal number, write the value as a string instead.
+    // The following test also handles NaN since comparisons are always false.
+    if (val > Float.NEGATIVE_INFINITY && val < Float.POSITIVE_INFINITY) {
+      writeFloat(name,s);
+    } else {
+      writeStr(name,s,false);
+    }
+  }
+
+
+  default void writeDouble(String name, double val) throws IOException {
+    String s = Double.toString(val);
+    // If it's not a normal number, write the value as a string instead.
+    // The following test also handles NaN since comparisons are always false.
+    if (val > Double.NEGATIVE_INFINITY && val < Double.POSITIVE_INFINITY) {
+      writeDouble(name,s);
+    } else {
+      writeStr(name,s,false);
+    }
+  }
+  default void writeBool(String name , Boolean val) throws IOException {
+    writeBool(name, val.toString());
+  }
+
+  @Override
+  default void writeMap(MapWriter mw) throws IOException {
+    //todo
+  }
+
+  @Override
+  default void writeIterator(IteratorWriter iw) throws IOException {
+    /*todo*/
+  }
+  default void indent() throws IOException {
+    if (doIndent()) indent(level());
+  }
+  int incLevel();
+  int decLevel();
+  TextWriter setIndent(boolean doIndent);
+  int level();
+  boolean doIndent();
+  default void indent(int lev) throws IOException {
+    getWriter().write(SolrJSONWriter.indentChars, 0, Math.min((lev<<1)+1, SolrJSONWriter.indentChars.length));
+  }
+
+}