You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uima.apache.org by sc...@apache.org on 2014/09/26 19:41:21 UTC

svn commit: r1627840 [1/3] - in /uima/uimaj/trunk/uimaj-json: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/uima/ src/main/java/org/apache/uima/json/ src/main/java/org/apache/uima/json/impl/ src/...

Author: schor
Date: Fri Sep 26 17:41:20 2014
New Revision: 1627840

URL: http://svn.apache.org/r1627840
Log:
[UIMA-4024] repackage json support for uimaj sdk with that project, but keep it separated from uimaj-core (so uimaj-core has no dependencies on it).  Clean up javadoc formatting.

Added:
    uima/uimaj/trunk/uimaj-json/pom.xml
    uima/uimaj/trunk/uimaj-json/src/
    uima/uimaj/trunk/uimaj-json/src/main/
    uima/uimaj/trunk/uimaj-json/src/main/java/
    uima/uimaj/trunk/uimaj-json/src/main/java/org/
    uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/
    uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/
    uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/
    uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonCasSerializer.java
    uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonMetaDataSerializer.java
    uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/impl/
    uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/impl/JsonContentHandlerJacksonWrapper.java
    uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/impl/MetaDataObjectSerializer_json.java
    uima/uimaj/trunk/uimaj-json/src/test/
    uima/uimaj/trunk/uimaj-json/src/test/java/
    uima/uimaj/trunk/uimaj-json/src/test/java/org/
    uima/uimaj/trunk/uimaj-json/src/test/java/org/apache/
    uima/uimaj/trunk/uimaj-json/src/test/java/org/apache/uima/
    uima/uimaj/trunk/uimaj-json/src/test/java/org/apache/uima/json/
    uima/uimaj/trunk/uimaj-json/src/test/java/org/apache/uima/json/JsonCasSerializerTest.java
    uima/uimaj/trunk/uimaj-json/src/test/java/org/apache/uima/json/JsonMetaDataObjectTest.java
    uima/uimaj/trunk/uimaj-json/src/test/resources/
    uima/uimaj/trunk/uimaj-json/src/test/resources/CASTests/
    uima/uimaj/trunk/uimaj-json/src/test/resources/CASTests/desc/
    uima/uimaj/trunk/uimaj-json/src/test/resources/CASTests/desc/casTestCaseTypesystem.xml
    uima/uimaj/trunk/uimaj-json/src/test/resources/CASTests/json/
    uima/uimaj/trunk/uimaj-json/src/test/resources/CASTests/json/expected/
    uima/uimaj/trunk/uimaj-json/src/test/resources/CASTests/json/expected/testTypesystem-plain.json
    uima/uimaj/trunk/uimaj-json/src/test/resources/CASTests/json/expected/testTypesystem.json
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/desc/
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/desc/allTypes.xml
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/desc/nameSpaceNeeded.xml
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/allValues.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/allValuesByType.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/allValuesByTypeOmits.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/allValuesNoIDNoTypes.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/allValuesNoIDs.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/allValuesNoTypes.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/allValuesOmits.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/delta.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/delta2.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/multipleViews.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/nameSpaceCollision.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/nameSpaceCollision2.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/nameSpaceCollision2Omits.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/nameSpaceCollision2pp.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/nameSpaceCollision2ppOmits.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/nameSpaceCollisionOmits.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/top.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/topExpandedNamesNoViews.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/topFeatRefsSupertypesNoViews.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/topNoContext.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/topNoContextNoViews.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/topWithDefaultViewOmits.txt
    uima/uimaj/trunk/uimaj-json/src/test/resources/CasSerialization/expected/topWithNamedViewOmits.txt
Modified:
    uima/uimaj/trunk/uimaj-json/   (props changed)

Propchange: uima/uimaj/trunk/uimaj-json/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Fri Sep 26 17:41:20 2014
@@ -0,0 +1,4 @@
+.settings
+target
+.classpath
+.project

Added: uima/uimaj/trunk/uimaj-json/pom.xml
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-json/pom.xml?rev=1627840&view=auto
==============================================================================
--- uima/uimaj/trunk/uimaj-json/pom.xml (added)
+++ uima/uimaj/trunk/uimaj-json/pom.xml Fri Sep 26 17:41:20 2014
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.uima</groupId>
+    <artifactId>uimaj-parent</artifactId>
+    <version>2.6.1-SNAPSHOT</version>
+    <relativePath/>  <!-- get rid of warning, no relative path possible -->
+  </parent>
+  <artifactId>uimaj-json</artifactId>
+  <name>Apache UIMA Base: ${project.artifactId}: JSON</name>
+  <description>JSON support for UIMA SDK</description>
+  
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/uima/uimaj/trunk/uimaj-json</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/uima/uimaj/trunk/uimaj-json</developerConnection>
+    <url>http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-json</url>
+  </scm>
+  
+  <properties>
+    <uimaScmProject>${project.artifactId}</uimaScmProject>
+  </properties>
+  
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.uima</groupId>
+      <artifactId>uimaj-core</artifactId>
+      <version>${project.parent.version}</version>
+    </dependency>
+  
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+      <version>2.4.2</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.uima</groupId>
+      <artifactId>uimaj-test-util</artifactId>
+      <version>${project.parent.version}</version>
+      <scope>test</scope>
+    </dependency>    
+  </dependencies>
+  
+  <build>
+    <pluginManagement>
+	    <plugins>
+	      <plugin>
+	          <groupId>org.apache.rat</groupId>
+	          <artifactId>apache-rat-plugin</artifactId>
+	          <executions>
+	            <execution>
+	              <id>default-cli</id>
+	              <configuration>
+	                <excludes>
+	                  <exclude>release.properties</exclude> <!-- generated file -->
+	                  <exclude>src/test/resources/CasSerialization/expected/*.txt</exclude>                  
+	                  <exclude>src/test/resources/CASTests/json/expected/*.json</exclude>              
+	               </excludes>
+	              </configuration>
+	            </execution>
+	          </executions>
+	      </plugin>
+	    </plugins>
+    </pluginManagement>
+    
+    <plugins>
+    </plugins>
+  </build>
+  
+</project>
\ No newline at end of file

Added: uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonCasSerializer.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonCasSerializer.java?rev=1627840&view=auto
==============================================================================
--- uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonCasSerializer.java (added)
+++ uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonCasSerializer.java Fri Sep 26 17:41:20 2014
@@ -0,0 +1,1261 @@
+/*
+ * 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.uima.json;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.uima.cas.ByteArrayFS;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Marker;
+import org.apache.uima.cas.Type;
+import org.apache.uima.cas.TypeSystem;
+import org.apache.uima.cas.impl.ByteArrayFSImpl;
+import org.apache.uima.cas.impl.CASImpl;
+import org.apache.uima.cas.impl.CasSerializerSupport;
+import org.apache.uima.cas.impl.CasSerializerSupport.CasDocSerializer;
+import org.apache.uima.cas.impl.CasSerializerSupport.CasSerializerSupportSerialize;
+import org.apache.uima.cas.impl.ListUtils;
+import org.apache.uima.cas.impl.LowLevelCAS;
+import org.apache.uima.cas.impl.TypeImpl;
+import org.apache.uima.cas.impl.TypeSystemImpl;
+import org.apache.uima.cas.impl.XmiSerializationSharedData.XmiArrayElement;
+import org.apache.uima.internal.util.XmlElementName;
+import org.apache.uima.json.impl.JsonContentHandlerJacksonWrapper;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.io.SerializedString;
+
+/**
+ * <h2>CAS serializer for JSON formats.</h2>
+ * <p>Writes a CAS in one of several JSON formats.</p>
+ *   
+ * <p>To use,</p>
+ * <ul>
+ *   <li>create an instance of this class,</li> 
+ *   <li>optionally) configure the instance, and then</li> 
+ *   <li>call serialize on the instance, optionally passing in additional parameters.</li></ul>
+ *   
+ *   
+ * <p>After the 1st 2 steps, the serializer instance may be used for multiple calls (on multiple threads) to
+ * the 3rd serialize step, if all calls use the same configuration.</p>
+ * 
+ * <p>There are "convenience" static serialize methods that do these three steps for common configurations.</p>
+ * 
+ * <p>Parameters can be configured in this instance (I), and/or as part of the serialize(S) call.</p>
+ * 
+ * <p>The parameters that can be configured are:</p>
+ * <ul>
+ *   <li>(S) The CAS to serialize
+ *   <li>(S) where to put the output - an OutputStream, Writer, or File</li>
+ *   <li>(I,S) a type system - (default null) if supplied, it is used to "filter" types and features that are serialized.  If provided, only 
+ *   those that exist in the passed in type system are included in the serialization</li>
+ *   <li>(I,S) a flag for prettyprinting - default false (no prettyprinting)</li>
+ * </ul>
+ * 
+ * <p>For Json serialization, additional configuration from the Jackson implementation can be configured</p>
+ * on 2 associated Jackson instances:  
+ *   <ul><li>JsonFactory</li> 
+ *       <li>JsonGenerator</li></ul>
+ * using the standard Jackson methods on the associated JsonFactory instance; 
+ * see the Jackson JsonFactory and JsonGenerator javadocs for details.
+ * 
+ * <p>These 2 Jackson objects are settable/gettable from an instance of this class.
+ * They are created if not supplied by the caller.</p>
+ * 
+ * <p>Once this instance is configured, the serialize method is called
+ * to serialized a CAS to an output.</p>
+ * 
+ * <p>Instances of this class must be used on only one thread while configuration is being done;
+ * afterwards, multiple threads may use the configured instance, to call serialize.</p>
+ */
+public class JsonCasSerializer {
+
+  private static final SerializedString FEATURE_REFS_NAME = new SerializedString("@featureRefs");
+  
+  private static final SerializedString FEATURE_BYTE_ARRAY_NAME = new SerializedString("@featureByteArrays");
+  
+  private static final SerializedString SUPER_TYPES_NAME = new SerializedString("@superTypes");
+    
+  private static final SerializedString JSON_CONTEXT_TAG_LOCAL_NAME = new SerializedString("@context");
+  
+  private static final SerializedString JSON_CAS_FEATURE_STRUCTURES = new SerializedString("@cas_feature_structures");
+  
+  private static final SerializedString JSON_CAS_VIEWS = new SerializedString("@cas_views");
+  
+  private static final SerializedString JSON_ID_ATTR_NAME = new SerializedString("@id");
+  
+  private static final SerializedString JSON_TYPE_ATTR_NAME = new SerializedString("@type");
+  
+  private static final SerializedString JSON_COLLECTION_ATTR_NAME = new SerializedString("@collection");
+  
+  private static final SerializedString JSON_VIEW_ADDED = new SerializedString("added_members");
+  private static final SerializedString JSON_VIEW_DELETED = new SerializedString("deleted_members");
+  private static final SerializedString JSON_VIEW_REINDEXED = new SerializedString("reindexed_members");
+  
+  /**
+   *  <p>This enum describes the kinds of JSON formats used for serializing Feature Structures
+   *  An individual Feature Structure is serialized as a JSON object, with feature-value pairs.</p>
+   *  
+   *  <p>The "type" and "id" are usually embedded as extra special features in the list of features, but
+   *  if that is not wanted, they can be turned off:</p>
+   *  <ul style="list-style-type:none">
+   *    <li>OMIT_ID</li>
+   *    <li>OMIT_TYPE</li></ul>
+   *  
+   *  <p>The type or ID can optionally be used as an key in an enclosing map, to make accessing things 
+   *  by these keys straightforward.  If you want the serialization to include an enclosing map, 
+   *  you pick from one (not both) of these:</p>
+   *  
+   *    <ul style="list-style-type:none"><li>INDEX_ID</li>
+   *        <li>INDEX_TYPE</li></ul>
+   *  
+   *  <p>Without the enclosing map, the collection of feature structures is serialized as a JSON array of
+   *  feature structure objects.</p> 
+   *  
+   *  <p>With the enclosing map, the key is the value of the index item, and the values are either:</p>
+   *    
+   *    <ul style="list-style-type:none"><li><p>a JSON ARRAY of feature structure objects, for KEY_BY_TYPE, 
+   *        since there can be multiple feature structures of a particular type, or</p></li>
+   *        
+   *        <li><p>one feature structure object, for KEY_BY_ID, since each feature structure has a unique ID</p></li></ul>
+   *       
+   *  <p>In the case of KEY_BY_TYPE, the feature structures are ordered like the UIMA built-in annotation index.</p>
+   *  
+   *  <p>Example of INDEX_ID:</p>
+   *  <pre>
+   *      { "123" : { "@id" : 123, "@type" : "type-name", feat : value, ... } 
+   *  </pre>
+   *      
+   *  <p>Example of INDEX_TYPE:</p>
+   *  <pre>
+   *      { "type-name" : [ { "@id" : 123, "@type" : "type-name", feat : value ... }, 
+   *                        { "@id" : 456, "@type" : "type-name", feat : value ... }
+   *                        ...
+   *                      ], 
+   *        ...
+   *      }
+   *  </pre>
+   */
+  public enum JsonCasFormat {
+    OMIT_TYPE,
+    OMIT_ID,
+    INDEX_TYPE,    // outputs each FS as "nnn"  : { "@type" : "foo", features : values ... }
+    INDEX_ID,    // outputs each FS as "type" : { "@id" : 123, features : values ... }
+  }
+  
+  /**
+   * <p>The serialization can optionally include context information in addition to the feature structures.</p>
+   * 
+   * <p>This context information is specified, per used-type.</p>
+   * 
+   * <p>It can be further subdivided into 3 parts:</p>
+   *   <ol>
+   *   <li>what their super types are.  This is needed in case the receiver needs to iterate over
+   *      a supertype (and all of its subtypes), e.g. an interator over all "Annotations".</li>
+   *   <li>which of their features are references to other feature structures.  This is needed
+   *      if the receiver wants to construct "graphs" of feature structures, with arbitrary links, 
+   *      back-links, cycles, etc.</li>
+   *   <li> whether or not to include the map from short type names to their fully qualified equivalents.</li></ol>
+   *
+   */
+  public enum JsonContextFormat {
+    omitContext,        includeContext,
+    omitSupertypes,     includeSuperTypes,
+    omitFeatureContext,    includeFeatureContext,
+    omitExpandedTypeNames, includeExpandedTypeNames,
+  }
+  
+  private final CasSerializerSupport css = new CasSerializerSupport();
+
+  private JsonFactory jsonFactory = null;
+
+  private final EnumSet<JsonCasFormat> jsonCasFormat = EnumSet.noneOf(JsonCasFormat.class);
+  
+  private boolean isWithContext = true;
+  private boolean isWithSupertypes = true;
+  private boolean isWithFeatureContext = true;
+  private boolean isWithExpandedTypeNames = true;
+  private boolean isWithViews = true;
+  private boolean isOmitDefaultValues = true;
+
+  
+  /***********************************************
+   *         C O N S T R U C T O R S             *  
+   ***********************************************/
+
+  /**
+   * Creates a new XmiCasSerializer
+   */
+  public JsonCasSerializer() {
+  }     
+  
+  /**************************************************
+   *                  J S O N                       *
+   **************************************************/
+  
+  /****************************************************
+   *  Static JSON Serializer methods for convenience  *
+   *                                                  *
+   *    Note: these are named jsonSerialize           *
+   *          The non-static methods                  *
+   *                are named serializeJson           *
+   ****************************************************/
+  
+  /**
+   * Serializes a CAS using JSON
+   * 
+   * @param aCAS
+   *          CAS to serialize.
+   * @param output
+   *          a File, OutputStream or Writer to which to write the XMI document
+   * 
+   * @throws IOException if there was an IOException
+   */
+  public static void jsonSerialize(CAS aCAS, Object output) throws IOException {  
+    jsonSerialize(aCAS, null, output, false, null);
+  }
+
+  /**
+   * Serializes a CAS to an output (File, OutputStream, XMI stream, or Writer). 
+   * The supplied typesystem filters the output
+   * 
+   * @param aCAS
+   *          CAS to serialize.
+   * @param aTargetTypeSystem
+   *          type system used for filtering what gets serialized. Any types or features not in the
+   *          target type system will not be serialized.  A null value indicates no filtering, that is, 
+   *          that all types and features will be serialized.
+   * @param output 
+   *          output (File, OutputStream, or Writer) to which to write the JSON document
+   * 
+   * @throws IOException if there was an IOException 
+   */
+  public static void jsonSerialize(CAS aCAS, TypeSystem aTargetTypeSystem, Object output)
+          throws IOException {
+    jsonSerialize(aCAS, aTargetTypeSystem, output, false, null);
+  }
+
+  /**
+   * Serializes a Delta CAS to an output (File, Writer, or OutputStream).  
+   * This version of this method allows many options to be configured.
+   *    
+   *    
+   * @param aCAS
+   *          CAS to serialize.
+   * @param aTargetTypeSystem
+   *          type system to which the produced XMI will conform. Any types or features not in the
+   *          target type system will not be serialized.  A null value indicates that all types and features
+   *          will be serialized.
+   * @param output
+   *          File, Writer, or OutputStream to which to write the JSON document
+   * @param aPrettyPrint
+   *          if true the JSON output will be formatted with newlines and indenting.  If false it will be unformatted.
+   * @param aMarker
+   *        an optional object used to determine which FeatureStructures and modifications were created after
+   *          the mark was set. Used to serialize a Delta CAS consisting of only new FSs and views and
+   *          preexisting FSs and Views that have been modified.  If null, full serialization is done.        
+   *          See the JavaDocs for {@link Marker} for details.
+   * @throws IOException if there was an IOException
+   */
+  public static void jsonSerialize(CAS aCAS, TypeSystem aTargetTypeSystem, Object output, boolean aPrettyPrint, Marker aMarker)
+          throws IOException {
+    JsonCasSerializer ser = new JsonCasSerializer();
+    ser.setFilterTypes((TypeSystemImpl)aTargetTypeSystem);
+    ser.setPrettyPrint(aPrettyPrint);
+    ser.setDeltaCas(aMarker);  
+    ser.serialize(aCAS, output);
+  } 
+    
+  /*************************************************************************************
+   * Multi-step api
+   * 
+   *   1) Create an instance of this class and use for configuration, specifying or defaulting
+   *          type system to use for filtering (default - no filtering)
+   *          prettyprinting (default - false)
+   *             
+   *       1b) Do any additional wanted configuration on the instance of this class
+   *          instance.prettyPrint(true/false);
+   *          instance.useJsonFactory(factory)
+   *          instance.filterTypes(typeSystem)
+   *          instance.errorHandler(errorHandler)
+   *          instance.jsonFormat(EnumSet.of(x, y, z)) - default is none of the settings
+   *          
+   *          instance.getGenerator() to further configure the generator if the defaults are not what is wanted.
+   *                    
+   *   2) call its serializeJson method, passing in the CAS, and an output (Writer/Outputstream/File)
+   *               
+   *************************************************************************************/
+  
+  /**
+   * Serialize a Cas to an Output, using configurations set on this instance.
+   *   Constructs a JsonContentHandlerJacksonWrapper, using configured JsonFactory and prettyprint settings if any
+   * @param cas - the CAS to serialize
+   * @param output - where the output goes, an OutputStream, Writer, or File
+   * @throws IOException if there was an IOException
+   */
+  public void serialize(CAS cas, Object output) throws IOException {
+    JsonContentHandlerJacksonWrapper jch;
+    try {
+      jch = new JsonContentHandlerJacksonWrapper(jsonFactory, output, css.isFormattedOutput);
+    } catch (SAXException e) {
+      throw new IOException(e);
+    }
+    serialize(cas, jch);
+  }
+  
+  /**
+   * Serialize a Cas to an Output configured in the passed in JsonContentHandlerJacksonWrapper
+   *   Constructs a new CasDocSerializer instance to do the serialization, 
+   *      configured using this class's Delta marker setting (if any)
+   * @param cas The CAS to serialize
+   * @param jch the configured content handler
+   * @throws IOException if there was an IOException 
+   */
+  public void serialize(CAS cas, JsonContentHandlerJacksonWrapper jch) throws IOException {
+      JsonDocSerializer ser = new JsonDocSerializer(jch, ((CASImpl) cas).getBaseCAS());
+      try {
+        ser.cds.needNameSpaces = false;
+        ser.cds.serialize();
+      } catch (Exception e) {
+        throw (IOException) e;
+      }
+  }
+  
+  /********************************************************
+   *   Routines to set/reset configuration                *
+   ********************************************************/
+  /**
+   * set or reset the pretty print flag (default is false)
+   * @param pp true to do pretty printing of output
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer setPrettyPrint(boolean pp) {
+    css.setPrettyPrint(pp);
+    return this;
+  }
+  
+  /**
+   * set which JsonFactory instance to use; if null, a new instance is used
+   *   this can be used to preconfigure the JsonFactory instance
+   * @param jsonFactory -
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer setJsonFactory(JsonFactory jsonFactory) {
+    this.jsonFactory = jsonFactory;
+    return this;
+  }
+  
+  /**
+   * pass in a type system to use for filtering what gets serialized;
+   * only those types and features which are defined this type system are included.
+   * @param ts the filter
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer setFilterTypes(TypeSystemImpl ts) {
+    css.setFilterTypes(ts);
+    return this;
+  }
+  
+  /**
+   * set the Marker to specify delta cas serialization
+   * @param m - the marker
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer setDeltaCas(Marker m) {
+    css.setDeltaCas(m);
+    return this;
+  }
+  
+  /**
+   * set an error handler to receive information about errors
+   * @param eh the error handler
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer setErrorHandler(ErrorHandler eh) {
+    css.setErrorHandler(eh);
+    return this;
+  }
+  
+  /**
+   * adds the style of Json formatting desired.
+   * @param format specifies the style
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer jsonCasFormatAdd(JsonCasFormat format) {
+    jsonCasFormat.add(format);
+    if (format == JsonCasFormat.INDEX_ID) {
+      jsonCasFormat.remove(JsonCasFormat.INDEX_TYPE);
+    } else if (format == JsonCasFormat.INDEX_TYPE) {
+      jsonCasFormat.remove(JsonCasFormat.INDEX_ID);
+    }
+    return this;
+  }
+  
+  /**
+   * removes the style of Json formatting desired.
+   * @param format specifies the style
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer jsonCasFormatRemove(JsonCasFormat format) {
+    jsonCasFormat.remove(format);
+    return this;
+  }
+  
+  /**
+   * sets which Json context format to use when serializing
+   * @param format the format to use for the serialization
+   *   Specifying the context flag also specifies all 3 subflags
+   *   Specifying one of the subflags as true sets the context flag to true if it isn't already
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer setJsonContext(JsonContextFormat format) {
+    switch (format) {
+    case omitContext: 
+      isWithContext = false;            
+      isWithSupertypes = false;                                                  
+      isWithFeatureContext = false;
+      isWithExpandedTypeNames = false; break;
+    case includeContext: 
+      isWithContext = true;
+      isWithSupertypes = true;
+      isWithFeatureContext = true;
+      isWithExpandedTypeNames = true; break;
+                                                        
+    case omitSupertypes: 
+      isWithSupertypes = false; break;
+    case includeSuperTypes: 
+      isWithSupertypes = true; 
+      isWithContext = true; break;
+      
+    
+    case omitFeatureContext: 
+      isWithFeatureContext = false; break;
+    case includeFeatureContext: 
+      isWithFeatureContext = true;
+      isWithContext = true; break;
+      
+    case omitExpandedTypeNames:
+      isWithExpandedTypeNames = false; break;
+    case includeExpandedTypeNames: 
+      isWithExpandedTypeNames = true; 
+      isWithContext = true; break;                             
+    }
+    return this;
+  }
+  
+  /**
+   * Causes the index information to be included in the serialization
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer setCasViews() {
+    return setCasViews(true);
+  }
+  
+  /**
+   * Sets whether or not to include which Feature Structures were indexed, by view
+   * @param includeViews true to include the index information
+   * @return the original instance, possibly updated
+   */
+  public JsonCasSerializer setCasViews(boolean includeViews) {
+    isWithViews = includeViews;
+    return this;
+  }
+  
+  public JsonCasSerializer setOmitDefaultValues(boolean omitDefaultValues) {
+    isOmitDefaultValues = omitDefaultValues;
+    return this;
+  }
+  
+  class JsonDocSerializer extends CasSerializerSupportSerialize {
+    
+    private final CasDocSerializer cds;
+    
+    private final JsonContentHandlerJacksonWrapper jch;
+
+    private final JsonGenerator jg;
+       
+    private final Set<Type> jsonRecordedSuperTypes = new HashSet<Type>();
+
+    private final Map<String, SerializedString> serializedStrings = new HashMap<String, SerializedString>();
+    
+    private final Map<String, XmlElementName> usedTypeName2XmlElementName; 
+   
+    private int lastEncodedTypeCode;
+    
+    private final boolean isOmitDefaultValues;
+    
+    private final boolean isWithContext;
+
+    private final boolean isWithExpandedTypeNames;
+
+    private final boolean isWithFeatureContext;
+
+    private final boolean isWithSupertypes;
+
+    private final boolean isWithViews;
+    
+    private final boolean isWithContextOrViews;
+    
+    private final boolean omitType;
+    private final boolean omitId;
+    private final boolean indexType;
+    private final boolean indexId;
+
+    private JsonDocSerializer(ContentHandler ch, CASImpl cas) {
+      cds = css.new CasDocSerializer(ch, cas, null, this);
+      this.isOmitDefaultValues = JsonCasSerializer.this.isOmitDefaultValues; 
+      boolean tempIsWithContext = JsonCasSerializer.this.isWithContext; 
+      isWithExpandedTypeNames = JsonCasSerializer.this.isWithExpandedTypeNames; 
+      isWithFeatureContext = JsonCasSerializer.this.isWithFeatureContext; 
+      isWithSupertypes = JsonCasSerializer.this.isWithSupertypes; 
+      isWithViews = JsonCasSerializer.this.isWithViews; 
+      jch = (JsonContentHandlerJacksonWrapper) ch;
+      jg = jch.getJsonGenerator();
+      isWithContext = (tempIsWithContext && !isWithSupertypes && !isWithFeatureContext && !isWithExpandedTypeNames) ? 
+          false : tempIsWithContext;
+      isWithContextOrViews = isWithContext || isWithViews;
+      usedTypeName2XmlElementName = new HashMap<String, XmlElementName>(cds.tsi.getNumberOfTypes());
+
+      omitType = JsonCasSerializer.this.jsonCasFormat.contains(JsonCasFormat.OMIT_TYPE);
+      omitId =   JsonCasSerializer.this.jsonCasFormat.contains(JsonCasFormat.OMIT_ID);
+      indexId   = JsonCasSerializer.this.jsonCasFormat.contains(JsonCasFormat.INDEX_ID);
+      indexType = indexId ? false : JsonCasSerializer.this.jsonCasFormat.contains(JsonCasFormat.INDEX_TYPE);
+    }
+    
+    protected void initializeNamespaces() {
+      if (cds.sharedData != null &&
+          (null != cds.sharedData.getOutOfTypeSystemElements() ||
+           cds.sharedData.hasOutOfTypeSystemArrayElements())) {
+        throw new UnsupportedOperationException("Can't do JSON serialization "
+            + "if there are out-of-type-system elements,"
+            + " because there's no type information available (needed for @context)");
+      }
+    }
+
+    protected void writeViews() throws Exception {
+      if (!isWithViews) {
+        return;
+      }
+      jch.writeNlJustBeforeNext();
+      jg.writeFieldName(JSON_CAS_VIEWS);
+      jg.writeStartObject();        
+    
+      cds.writeViewsCommons(); // encodes cas.sofaCount + 1 elements
+      jg.writeEndObject();  // and end of views property 
+    }
+   
+    protected void writeFeatureStructures(int elementCount /* not used */ ) throws Exception{
+      jch.withoutNl();  // set up prettyprint mode so this class controls it
+      if (isWithContextOrViews) {
+        jg.writeStartObject();  // container for context, fss, and views
+      }
+      if (isWithContext) {
+        serializeJsonLdContext();
+      }
+      if (isWithContextOrViews) {
+        jch.writeNlJustBeforeNext();       
+        jg.writeFieldName(JSON_CAS_FEATURE_STRUCTURES);
+      }
+      
+      if (indexId || indexType) {
+        jg.writeStartObject();  // either outer object, or object of JSON_CAS_FEATURE_STRUCTURES
+      } else {
+        jg.writeStartArray();
+      }
+      
+      if (indexType) { 
+        Integer[] allFss = cds.collectAllFeatureStructures();
+        Arrays.sort(allFss, cds.sortFssByType);
+        encodeAllFss(allFss);
+      } else {
+        cds.encodeIndexed();
+        cds.encodeQueued();
+      }
+      
+      // out of type system data not supported - no type info for @context available
+//      if (!cds.isDelta) {  // if delta, the out-of-type-system elements, are guaranteed not to be modified
+//        serializeOutOfTypeSystemElements(); //encodes sharedData.getOutOfTypeSystemElements().size() elements
+//      }
+      
+      if (indexId || indexType) {
+        jg.writeEndObject();  // either outer object, or object of JSON_CAS_FEATURE_STRUCTURES
+      } else {
+        jg.writeEndArray();
+      }
+    }
+    
+    protected void writeEndOfSerialization() throws IOException {
+      if (isWithContextOrViews) {
+        jg.writeEndObject(); // wrapper of @context and cas
+      }
+      jg.flush();
+    }
+   
+    
+    protected void writeView(int sofaAddr, int[] members) throws IOException {
+      jch.writeNlJustBeforeNext();
+      String xmiId = (0 == sofaAddr) ? "0" : cds.getXmiId(sofaAddr);
+      jg.writeArrayFieldStart(xmiId);
+      writeViewMembers(members);
+      //check for out-of-typesystem members
+      if (cds.sharedData != null) {
+        String sofaXmiId = cds.getXmiId(sofaAddr);
+        List<String> ootsMembers = cds.sharedData.getOutOfTypeSystemViewMembers(sofaXmiId);
+        jch.writeNlJustBeforeNext();
+        writeViewMembers(ootsMembers);
+      }
+
+      jg.writeEndArray();
+    }
+    
+    private void writeViewForDeltas(SerializedString kind, int[] deltaMembers) throws IOException {
+      jg.writeFieldName(kind);
+      jg.writeStartArray();
+      writeViewMembers(deltaMembers);   
+      jg.writeEndArray();
+    }
+    
+    protected void writeView(int sofaAddr, int[] added, int[] deleted, int[] reindexed) throws IOException {
+      jch.writeNlJustBeforeNext();
+      jg.writeFieldName(Integer.toString(sofaAddr));
+      jg.writeStartObject();
+      writeViewForDeltas(JSON_VIEW_ADDED, added);
+      writeViewForDeltas(JSON_VIEW_DELETED, deleted);
+      writeViewForDeltas(JSON_VIEW_REINDEXED, reindexed);      
+      jg.writeEndObject();
+    }
+    
+    private void writeViewMembers(int[] members) throws IOException {
+      int nextBreak = CasSerializerSupport.PP_ELEMENTS;
+      int i = 0;
+      for (int member : members) {
+        int xmiId = cds.getXmiIdAsInt(member);
+        if (xmiId == 0) {
+          continue;
+        }
+        if (i++ > nextBreak) {
+          jch.writeNlJustBeforeNext();
+          nextBreak += CasSerializerSupport.PP_ELEMENTS;
+        }
+        jg.writeNumber(xmiId);
+      }
+    }
+
+    /*
+     * version for oots data
+     */
+    
+    private void writeViewMembers(List<String> members) throws IOException {
+      int nextBreak = CasSerializerSupport.PP_ELEMENTS;
+      int i = 0;
+      for (String xmiId : members) {
+        if (null == xmiId || xmiId.length() == 0) {
+          continue;
+        }
+        if (i++ > nextBreak) {
+          jch.writeNlJustBeforeNext();
+          nextBreak += CasSerializerSupport.PP_ELEMENTS;
+        }
+        jg.writeNumber(Integer.parseInt(xmiId));
+      }
+    }
+    /**
+     * JSON: serialize context info
+     * 
+     *    (for namespaces - may be omitted if not needed for disambiguation)
+     *    "nameSpacePrefix" : IRI-for-namespace
+     *    
+     *    (for each used type)
+     *    "typeName - or nameSpace:typeName" : { 
+     *       "@id" : expanded-name-as-IRI,
+     *       "@superTypes" : [xxx, yyy, zzz, uima.cas.TOP],
+     *       "@featureRefs" : [ "featName1", "featName2"],
+     *       "@featureByteArrays" : [ "featName1", "featName2"],
+     *   
+     *   @featureRefs: the named feature values are a number or an array of numbers, all of which
+     *                 are to be interpreted as an ID of another serialized Feature Structure (unless 0, 
+     *                 which is like a null reference)
+     *   @featureByteArrays: the named feature values are to be interpreted as base64 encoded byte arrays
+     *   
+     *    superType values are longNames
+     * @throws IOException 
+     */
+    
+    private void serializeJsonLdContext() throws IOException {  
+      //   @context :  { n : v ... }}
+      jg.writeFieldName(JSON_CONTEXT_TAG_LOCAL_NAME);
+//      jch.setDoNL();
+      jg.writeStartObject();
+      
+//      if (needNameSpaces) {
+//        serializeNameSpaceInfo();
+//      }
+      
+      for (TypeImpl ti : cds.getSortedUsedTypes()) {
+        jch.writeNlJustBeforeNext();      
+        jg.writeFieldName(getSerializedTypeName(ti.getCode()));
+        jg.writeStartObject();
+        jch.writeNlJustBeforeNext();
+        if (isWithExpandedTypeNames) {
+          jg.writeFieldName(JSON_ID_ATTR_NAME);  // form for using SerializedString
+          jg.writeString(ti.getName());
+        }
+        if (isWithFeatureContext) {
+          addJsonFeatContext(ti);
+        }
+        if (isWithSupertypes) {
+          addJsonSuperTypes(ti);
+        }
+        jg.writeEndObject();  // end of one type
+      }
+      jg.writeEndObject();  // end of context
+    }
+    
+    /**
+     * Adds the ", @featureRefs" : [ "featName1", "featName2"] 
+     * for features that are being serialized as references. 
+     * 
+     * Adds the ", @featureByteArrays" : [ "featName1", "featName2"] 
+     * for features that are serialized as base64-encoded byte arrays 
+     * 
+     * @param type the type for which to generate the feature context info 
+     * @throws IOException 
+     */
+    private void addJsonFeatContext(TypeImpl type) throws IOException {
+      final int typeCode = type.getCode();
+      final int[] feats = cds.tsi.ll_getAppropriateFeatures(typeCode);
+      boolean started = false;
+      List<SerializedString> byteArrayFeatures = null;
+      for (int featCode : feats) {
+        final int fsClass = cds.classifyType(cds.tsi.range(featCode));
+        
+        if (isRefToFS(fsClass, featCode)) {
+          if (!started) {
+            jch.writeNlJustBeforeNext();
+            jg.writeFieldName(FEATURE_REFS_NAME);
+            jg.writeStartArray();
+            started = true; 
+          }
+          jg.writeString(getShortFeatureName(featCode));          
+        } else if (fsClass == LowLevelCAS.TYPE_CLASS_BYTEARRAY) {
+          if (byteArrayFeatures == null) {
+            byteArrayFeatures = new ArrayList<SerializedString>();
+          }
+          byteArrayFeatures.add(getShortFeatureName(featCode));
+        }
+        
+      }
+      if (started) {
+        jg.writeEndArray();
+      } 
+      if (byteArrayFeatures != null) {
+        jch.writeNlJustBeforeNext();
+        jg.writeFieldName(FEATURE_BYTE_ARRAY_NAME);
+        jg.writeStartArray();
+        for (SerializedString ss : byteArrayFeatures) {
+          jg.writeString(ss);
+        }
+        jg.writeEndArray();
+      }
+    }
+    
+    private SerializedString getShortFeatureName(int featCode) {
+      return getSerializedString(cds.tsi.ll_getFeatureForCode(featCode).getShortName());
+    }
+    
+    private boolean isMultRef(int featCode) {
+      return cds.tsi.ll_getFeatureForCode(featCode).isMultipleReferencesAllowed();
+    }
+    
+    /**
+     * Add superType chain to required type, up to point already covered
+     * @throws IOException 
+     */
+    private void addJsonSuperTypes(TypeImpl ti) throws IOException {
+      jch.writeNlJustBeforeNext();
+      jg.writeFieldName(SUPER_TYPES_NAME);
+      jg.writeStartArray();
+      for (TypeImpl parent = (TypeImpl) ti.getSuperType(); 
+           parent != null; 
+           parent = (TypeImpl) parent.getSuperType()) {
+        jg.writeString(parent.getName());
+        if (!jsonRecordedSuperTypes.add(parent)) {
+          break;  // if this item already output
+        }
+      }
+      jg.writeEndArray();
+    }
+
+    private void encodeAllFss(Integer[] allFss) throws IOException {
+      lastEncodedTypeCode = -1;
+      try {
+        for (Integer i : allFss) {
+          cds.encodeFS(i);
+        }
+      } catch (Exception e) {
+        throw (IOException) e;
+      }
+      if (lastEncodedTypeCode != -1) {
+        jg.writeEndArray();
+      }
+    }
+    
+    private SerializedString getSerializedTypeName(int typeCode) {
+      XmlElementName xe = cds.typeCode2namespaceNames[typeCode];
+      return getSerializedString(xe.qName);
+    }
+    
+    private SerializedString getSerializedString(String s) {
+      SerializedString ss = serializedStrings.get(s);
+      if (ss == null) {
+        ss = new SerializedString(s);
+        serializedStrings.put(s, ss);
+      }
+      return ss;
+    }
+
+    protected void enqueueIncoming() {}
+    
+    protected void enqueueNonsharedMultivaluedFS() {}
+    
+    protected void checkForNameCollision(XmlElementName xmlElementName) {
+      XmlElementName xel    = usedTypeName2XmlElementName.get(xmlElementName.localName);
+      if (xel != null) {
+        if (xel.nsUri.equals(xmlElementName.nsUri)) {  // nsUri is the fully qualified name
+          return;  // don't need name spaces yet
+        } else {
+          addNameSpace(xel);
+          addNameSpace(xmlElementName);
+//          usedTypeName2XmlElementName.clear();  // not needed anymore
+          return;
+        }
+      }
+      usedTypeName2XmlElementName.put(xmlElementName.localName, xmlElementName);
+      return;
+    }
+    
+    protected void writeFsStart(int addr, int typeCode) throws IOException {
+
+      if (indexId) {
+        jch.writeNlJustBeforeNext();
+        jg.writeFieldName(cds.getXmiId(addr));
+        jg.writeStartObject();  // start of feat : value
+      } else if (indexType) { // fs's as arrays under typeName
+        if (typeCode != lastEncodedTypeCode) {
+          if (lastEncodedTypeCode != -1) {
+            // close off previous Array
+            jg.writeEndArray();
+          }
+          lastEncodedTypeCode = typeCode;
+          jch.writeNlJustBeforeNext();
+          jg.writeFieldName(getSerializedTypeName(typeCode));
+          jg.writeStartArray();
+        }
+        jch.writeNlJustBeforeNext();
+        jg.writeStartObject();  // start of feat : value
+      } else { // no index
+        jch.writeNlJustBeforeNext();
+        jg.writeStartObject();
+      }
+    }
+    
+    private void maybeWriteIdFeat(int addr) throws IOException {
+      if (!omitId) {
+        jg.writeFieldName(JSON_ID_ATTR_NAME);
+        jg.writeNumber(cds.getXmiIdAsInt(addr));
+      }
+    }
+    
+    private void maybeWriteTypeFeat(int typeCode) throws IOException {
+      if (!omitType) {
+        jg.writeFieldName(JSON_TYPE_ATTR_NAME);
+        jg.writeString(getSerializedTypeName(typeCode));
+      }
+    }
+
+    protected void writeFs(int addr, int typeCode) throws IOException {
+      writeFsOrLists(addr, typeCode, false);
+    }
+    
+    protected void writeListsAsIndividualFSs(int addr, int typeCode) throws IOException {
+      writeFsOrLists(addr, typeCode, true);
+    }
+    
+    private void writeFsOrLists(int addr, int typeCode, boolean isListAsFSs) throws IOException {
+      final int[] feats = cds.tsi.ll_getAppropriateFeatures(typeCode);
+      
+      maybeWriteIdFeat(addr);
+      maybeWriteTypeFeat(typeCode);
+      
+      for (final int featCode : feats) {
+
+        if (cds.isFiltering) {
+          // skip features that aren't in the target type system
+          String fullFeatName = cds.tsi.ll_getFeatureForCode(featCode).getName();
+          if (cds.filterTypeSystem.getFeatureByFullName(fullFeatName) == null) {
+            continue;
+          }
+        }
+        
+        final SerializedString serializedFeatName = getShortFeatureName(featCode);
+        final int featAddr = addr + cds.cas.getFeatureOffset(featCode);
+        final int featValRaw = cds.cas.getHeapValue(featAddr);
+        final int featureClass = cds.classifyType(cds.tsi.range(featCode));
+        
+//        jg.writeFieldName(featName); // not done here, because if null feat can be omitted
+        
+        switch (featureClass) {
+        
+        case LowLevelCAS.TYPE_CLASS_BYTE:
+        case LowLevelCAS.TYPE_CLASS_SHORT:  
+        case LowLevelCAS.TYPE_CLASS_INT:
+          if (featValRaw == 0 && isOmitDefaultValues) continue;
+          jg.writeFieldName(serializedFeatName);
+          jg.writeNumber(featValRaw);
+          break;
+
+        case LowLevelCAS.TYPE_CLASS_FS:
+          if (featValRaw == 0/* && isOmitDefaultValues*/) continue;
+          jg.writeFieldName(serializedFeatName);
+          jg.writeNumber(cds.getXmiIdAsInt(featValRaw));
+          break;
+  
+        case LowLevelCAS.TYPE_CLASS_LONG:
+          final long longVal = cds.cas.ll_getLongValue(featValRaw);
+          if (longVal == 0L && isOmitDefaultValues) continue;
+          jg.writeFieldName(serializedFeatName);
+          jg.writeNumber(longVal);
+          break;
+          
+        case LowLevelCAS.TYPE_CLASS_FLOAT:
+          final float floatVal = CASImpl.int2float(featValRaw);
+          if (floatVal == 0.F && isOmitDefaultValues) continue;
+          jg.writeFieldName(serializedFeatName);
+          jg.writeNumber(floatVal);
+          break;
+          
+        case LowLevelCAS.TYPE_CLASS_DOUBLE:
+          final double doubleVal = cds.cas.ll_getDoubleValue(addr, featCode);
+          if (doubleVal == 0L && isOmitDefaultValues) continue;
+          jg.writeFieldName(serializedFeatName);
+          jg.writeNumber(doubleVal);
+          break;
+          
+        case LowLevelCAS.TYPE_CLASS_BOOLEAN:
+          jg.writeFieldName(serializedFeatName);
+          jg.writeBoolean(cds.cas.ll_getBooleanValue(addr, featCode));           
+          break; 
+        
+        case LowLevelCAS.TYPE_CLASS_STRING:
+          if (featValRaw == 0 /*&& isOmitDefaultValues*/) continue; 
+          jg.writeFieldName(serializedFeatName);
+          jg.writeString(cds.cas.getStringForCode(featValRaw));
+          break; 
+            
+        // all other fields (arrays, lists, fsRefs) can be null and are omitted if so  
+        default: 
+          if (featValRaw != CASImpl.NULL /*|| !isOmitDefaultValues*/) {
+            
+            jg.writeFieldName(serializedFeatName);
+
+            if (featureClass == LowLevelCAS.TYPE_CLASS_INTARRAY ||
+                featureClass == LowLevelCAS.TYPE_CLASS_FLOATARRAY ||
+                featureClass == LowLevelCAS.TYPE_CLASS_BOOLEANARRAY ||
+                featureClass == LowLevelCAS.TYPE_CLASS_BYTEARRAY ||
+                featureClass == LowLevelCAS.TYPE_CLASS_SHORTARRAY ||
+                featureClass == LowLevelCAS.TYPE_CLASS_LONGARRAY ||
+                featureClass == LowLevelCAS.TYPE_CLASS_DOUBLEARRAY ||
+                featureClass == LowLevelCAS.TYPE_CLASS_STRINGARRAY ||
+                featureClass == LowLevelCAS.TYPE_CLASS_FSARRAY) {
+              
+              if (isMultRef(featCode)) {
+                jg.writeNumber(cds.getXmiIdAsInt(featValRaw));
+              } else {
+                writeJsonArrayValues(featValRaw, featureClass);
+              }
+              
+            } else if (featureClass == CasSerializerSupport.TYPE_CLASS_INTLIST ||
+                       featureClass == CasSerializerSupport.TYPE_CLASS_FLOATLIST ||
+                       featureClass == CasSerializerSupport.TYPE_CLASS_STRINGLIST ||
+                       featureClass == CasSerializerSupport.TYPE_CLASS_FSLIST) {
+
+              if (isListAsFSs || isMultRef(featCode)) {
+                jg.writeNumber(cds.getXmiIdAsInt(featValRaw));
+              } else {
+                writeJsonListValues(featValRaw);
+              }              
+            } else {  // is error
+              throw new RuntimeException("Invalid State, featureClass was "+ featureClass);
+            }
+          }  // end of default case with non-null values
+        }  // end of switch
+      } // end of loop over all features
+    }
+    
+    protected void writeArrays(int addr, int typeCode, int typeClass) throws IOException {
+      maybeWriteIdFeat(addr);
+      maybeWriteTypeFeat(typeCode);
+
+      jg.writeFieldName(JSON_COLLECTION_ATTR_NAME);            
+      writeJsonArrayValues(addr, typeClass);
+    }
+    
+    protected void writeEndOfIndividualFs() throws IOException {
+      jg.writeEndObject();
+    }
+
+    // writes a set of values in a JSON array
+    // or null if the reference to the UIMA array is actually null
+    // 0 length arrays are written as [] 
+    private void writeJsonArrayValues(int addr, int arrayType) throws IOException {
+      if (addr == CASImpl.NULL) {
+        jg.writeNull();
+        return;
+      }
+      
+      final int size = cds.cas.ll_getArraySize(addr);
+
+      if (arrayType == LowLevelCAS.TYPE_CLASS_BYTEARRAY) {
+        // special case for byte arrays: 
+        // serialize using standard JACKSON/JSON binary serialization
+        // (doing extra copy to avoid figuring out the impl details)
+        ByteArrayFS byteArrayFS = new ByteArrayFSImpl(addr, cds.cas);
+        int length = byteArrayFS.size();
+        byte[] byteArray = new byte[length];
+        byteArrayFS.copyToArray(0, byteArray, 0, length);
+      
+        jg.writeBinary(byteArray);
+        
+      } else {
+        jg.writeStartArray();
+        int pos = cds.cas.getArrayStartAddress(addr);
+        
+        if (arrayType == LowLevelCAS.TYPE_CLASS_FSARRAY) {
+        
+          List<XmiArrayElement> ootsArrayElementsList = cds.sharedData == null ? null : 
+            cds.sharedData.getOutOfTypeSystemArrayElements(addr);
+          int ootsIndex = 0;
+  
+          for (int j = 0; j < size; j++) {  // j used to id the oots things
+            int heapValue = cds.cas.getHeapValue(pos++);
+  
+            if (heapValue == CASImpl.NULL) {
+              // this null array element might have been a reference to an 
+              // out-of-typesystem FS, which, when deserialized, was replaced with NULL,
+              // so check the ootsArrayElementsList
+              boolean found = false;
+              if (ootsArrayElementsList != null) {
+                
+                while (ootsIndex < ootsArrayElementsList.size()) {
+                  XmiArrayElement arel = ootsArrayElementsList.get(ootsIndex++);
+                  if (arel.index == j) {
+                    jg.writeNumber(Integer.parseInt(arel.xmiId));
+                    found = true;
+                    break;
+                  }                
+                }
+              }
+              if (!found) {
+                jg.writeNumber(0);
+              }
+              
+            // else, not null FS ref  
+            } else {
+              if (cds.isFiltering) { // return as null any references to types not in target TS
+                String typeName = cds.tsi.ll_getTypeForCode(cds.cas.getHeapValue(addr)).getName();
+                if (cds.filterTypeSystem.getType(typeName) == null) {
+                  heapValue = CASImpl.NULL;
+                }
+              }
+              jg.writeNumber(cds.getXmiIdAsInt(heapValue));
+            }
+          } // end of loop over all refs in FS array
+          
+        } else {
+          for (int i = 0; i < size; i++) {
+            if (arrayType == LowLevelCAS.TYPE_CLASS_BOOLEANARRAY) {
+              jg.writeBoolean(cds.cas.ll_getBooleanArrayValue(addr, i));
+            } else if (arrayType == LowLevelCAS.TYPE_CLASS_STRINGARRAY) {
+              jg.writeString(cds.cas.ll_getStringArrayValue(addr, i));
+  //          } else if (arrayType == LowLevelCAS.TYPE_CLASS_BYTEARRAY) {
+  //            jg.writeNumber(cds.cas.ll_getByteArrayValue(addr, i));
+            } else if (arrayType == LowLevelCAS.TYPE_CLASS_SHORTARRAY) {
+              jg.writeNumber(cds.cas.ll_getShortArrayValue(addr, i));
+            } else if (arrayType == LowLevelCAS.TYPE_CLASS_INTARRAY) {
+              jg.writeNumber(cds.cas.ll_getIntArrayValue(addr, i));
+            } else if (arrayType == LowLevelCAS.TYPE_CLASS_LONGARRAY) {
+              jg.writeNumber(cds.cas.ll_getLongArrayValue(addr, i));
+            } else if (arrayType == LowLevelCAS.TYPE_CLASS_FLOATARRAY) {
+              jg.writeNumber(cds.cas.ll_getFloatArrayValue(addr, i));
+            } else {
+              jg.writeNumber(cds.cas.ll_getDoubleArrayValue(addr, i));
+            }
+          }
+        }
+        jg.writeEndArray();
+      }
+    }
+    
+    // a null ref is written as null
+    // an empty list is written as []
+    /**
+     * Only called if no sharing of list nodes exists.
+     * Only called for list nodes referred to by Feature value slots in some non-list FS.
+
+     * @param curNode the address of the start of the list
+     * @throws IOException
+     */
+    private void writeJsonListValues(int curNode) throws IOException {
+      if (curNode == CASImpl.NULL) {
+//        jg.writeNull();
+//        return;
+        throw new RuntimeException("never happen");
+      }
+      final ListUtils listUtils = cds.listUtils;
+      final int startNodeType = cds.cas.getHeapValue(curNode);
+
+      int headFeat = listUtils.getHeadFeatCode(startNodeType);
+      int tailFeat = listUtils.getTailFeatCode(startNodeType);
+      int neListType = listUtils.getNeListType(startNodeType);  // non-empty
+           
+      jg.writeStartArray();
+      while (curNode != CASImpl.NULL) { 
+         
+        final int curNodeType = cds.cas.getHeapValue(curNode);
+        if (curNodeType != neListType) { // if not "non-empty"
+          break;  // would be the end element.  a 0 is also treated as an end element
+        }
+        
+        final int val = cds.cas.getHeapValue(curNode + cds.cas.getFeatureOffset(headFeat));
+
+        if (curNodeType == listUtils.neStringListType) {
+          jg.writeString(cds.cas.getStringForCode(val));
+        } else if (curNodeType == listUtils.neFloatListType) {
+          jg.writeNumber(CASImpl.int2float(val));
+        } else if (curNodeType == listUtils.neFsListType) {
+          jg.writeNumber(cds.getXmiIdAsInt(val));
+        } else {  // for ints 
+          jg.writeNumber(val);
+        }
+
+        curNode = cds.cas.getHeapValue(curNode + cds.cas.getFeatureOffset(tailFeat));
+      }
+      jg.writeEndArray();
+    
+    }
+    
+    /**
+     * Return true if the range of the feature is a fsRef, or a 
+     * collection of fsRefs (array or list), where the feature is marked
+     * multiple references allowed.
+     *     
+     * Return false for other primitives, or collections (arrays or lists) of primitives
+     * where the collection (feature) is marked as multipleReferencesAllowed = false
+     *  
+     *   
+     * @param fsClass the class of the feature
+     * @param featCode the feature code
+     * @return true if the serialization is being done by having the value of this feature be a reference id
+     *              or (in the case of embedded collections, where the collection items are feature references
+     */
+   
+    private boolean isRefToFS(int fsClass, int featCode) {
+      switch (fsClass) {
+        case LowLevelCAS.TYPE_CLASS_FS: 
+        case LowLevelCAS.TYPE_CLASS_FSARRAY: 
+        case CasSerializerSupport.TYPE_CLASS_FSLIST:
+          return true;
+          
+        case LowLevelCAS.TYPE_CLASS_INTARRAY:
+        case LowLevelCAS.TYPE_CLASS_FLOATARRAY:
+        case LowLevelCAS.TYPE_CLASS_STRINGARRAY:
+        case LowLevelCAS.TYPE_CLASS_BOOLEANARRAY:
+        case LowLevelCAS.TYPE_CLASS_BYTEARRAY:
+        case LowLevelCAS.TYPE_CLASS_SHORTARRAY:
+        case LowLevelCAS.TYPE_CLASS_LONGARRAY:
+        case LowLevelCAS.TYPE_CLASS_DOUBLEARRAY:
+        case CasSerializerSupport.TYPE_CLASS_INTLIST:
+        case CasSerializerSupport.TYPE_CLASS_FLOATLIST:
+        case CasSerializerSupport.TYPE_CLASS_STRINGLIST: 
+          // we have refs only if the feature has
+          // multipleReferencesAllowed = true
+          return isMultRef(featCode);   
+        
+        default:  // for primitives
+          return false;
+      }
+    }
+
+    /**
+     * Converts a UIMA-style dotted type name to the element name that should be used in the
+     * serialization. The XMI element name consists of three parts - the Namespace URI, the Local
+     * Name, and the QName (qualified name).
+     * 
+     * @param uimaTypeName
+     *          a UIMA-style dotted type name
+     * @return a data structure holding the three components of the XML element name
+     */
+    protected XmlElementName uimaTypeName2XmiElementName(String uimaTypeName) {
+      // split uima type name into namespace and short name
+      String shortName;
+      final int lastDotIndex = uimaTypeName.lastIndexOf('.');
+      if (lastDotIndex == -1) { // no namespace
+        shortName = uimaTypeName;
+      } else {
+        shortName = uimaTypeName.substring(lastDotIndex + 1);
+      }
+      // convert short name to shared string, without interning, reduce GCs
+      shortName = cds.getUniqueString(shortName);
+
+      return new XmlElementName(uimaTypeName, shortName, shortName);  // use short name for qname until namespaces needed
+    }
+    
+    /**
+     * Called to generate a new namespace prefix and add it to this element - due to a collision
+     * @param xmlElementName
+     */
+    protected void addNameSpace(XmlElementName xmlElementName) {
+      if (xmlElementName.qName.equals(xmlElementName.localName)) {  // may have already had namespace added
+        // split uima type name into namespace and short name
+        String uimaTypeName = xmlElementName.nsUri;
+        String shortName = xmlElementName.localName;
+        final int lastDotIndex = uimaTypeName.lastIndexOf('.');
+  
+        // determine what namespace prefix to use
+        String prefix = cds.getNameSpacePrefix(uimaTypeName, uimaTypeName, lastDotIndex);
+        xmlElementName.qName = cds.getUniqueString(prefix + ':' + shortName);
+      }
+    }
+    
+  }
+
+}

Added: uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonMetaDataSerializer.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonMetaDataSerializer.java?rev=1627840&view=auto
==============================================================================
--- uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonMetaDataSerializer.java (added)
+++ uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/JsonMetaDataSerializer.java Fri Sep 26 17:41:20 2014
@@ -0,0 +1,158 @@
+/*
+ * 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.uima.json;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+
+import org.apache.uima.json.impl.JsonContentHandlerJacksonWrapper;
+import org.apache.uima.json.impl.MetaDataObjectSerializer_json;
+import org.apache.uima.resource.metadata.impl.MetaDataObject_impl;
+import org.apache.uima.resource.metadata.impl.MetaDataObject_impl.SerialContext;
+import org.apache.uima.util.XMLizable;
+import org.xml.sax.SAXException;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+
+public class JsonMetaDataSerializer {
+
+  /**
+   * Serialize to a writer
+   * @param object - the instance of an XMLizable to serialize
+   * @param aWriter - where the output goes
+   * @throws SAXException - wrapping an IOException, probably
+   */
+  public static void toJSON(XMLizable object, Writer aWriter) throws SAXException {
+    toJSON(object, aWriter, false);
+  }
+
+  /**
+   * Serialize to a writer
+   * @param object - the instance of an XMLizable to serialize
+   * @param aWriter - where the output goes
+   * @param isFormattedOutput true for pretty printing
+   * @throws SAXException - wrapping an IOException, probably
+   */
+  public static void toJSON(XMLizable object, Writer aWriter, boolean isFormattedOutput) throws SAXException {
+    JsonGenerator jg;
+    try {
+      jg = new JsonFactory().createGenerator(aWriter);
+    } catch (IOException e) {
+      throw new SAXException(e);
+    }
+    
+    toJSON(object, jg, isFormattedOutput);
+  }
+ 
+  /**
+   * Serialize use a specific instance of a JsonGenerator which encapsulates where the output goes
+   * @param object - the instance of an XMLizable to serialize
+   * @param jg the generator to use
+   * @param isFormattedOutput true for pretty printing
+   * @throws SAXException - wrapping an IOException, probably
+   */
+  public static void toJSON(XMLizable object, JsonGenerator jg, boolean isFormattedOutput) throws SAXException {
+    JsonContentHandlerJacksonWrapper jch;
+    SerialContext sc = MetaDataObject_impl.serialContext.get();
+    
+    boolean setContext = false;
+    if (null == sc) {
+      jch = new JsonContentHandlerJacksonWrapper(jg, isFormattedOutput);
+      sc = new SerialContext(jch, new MetaDataObjectSerializer_json(jch));
+      MetaDataObject_impl.serialContext.set(sc);
+      setContext = true;
+    } else {
+      jch = (JsonContentHandlerJacksonWrapper) sc.ch;
+    }
+    
+    try {
+      jch.withoutNl();
+    
+      object.toXML(jch);
+    
+      jg.flush();
+    } catch (IOException e) {
+      throw new SAXException(e);
+    } finally {
+      if (setContext) {
+        MetaDataObject_impl.serialContext.remove();
+      }
+    }
+  }
+
+  /**
+   * Writes out this object's JSON representation.
+   * @param object - the instance of an XMLizable to serialize
+   * @param aOutputStream
+   *          an OutputStream to which the JSON will be written
+   * @throws SAXException - wrapping an IOException, probably
+   */
+  public static void toJSON(XMLizable object, OutputStream aOutputStream) throws SAXException {
+    toJSON(object, aOutputStream, false);
+  }
+
+  /**
+   * 
+   * @param object - the instance of an XMLizable to serialize
+   * @param aOutputStream
+   *          an OutputStream to which the JSON will be written
+   * @param isFormattedOutput true for pretty printing
+   * @throws SAXException - wrapping an IOException, probably
+   */
+  public static void toJSON(XMLizable object, OutputStream aOutputStream, boolean isFormattedOutput) throws SAXException {
+    try {
+      JsonGenerator jg = new JsonFactory().createGenerator(aOutputStream);
+      toJSON(object, jg, isFormattedOutput);
+    } catch (IOException e) {
+      throw new SAXException(e);
+    }
+  }
+
+  /**
+   * 
+   * @param object - the instance of an XMLizable to serialize
+   * @param file where the output goes
+   * @throws SAXException - wrapping an IOException, probably
+   */
+  public static void toJSON(XMLizable object, File file) throws SAXException {
+    toJSON(object, file, false);
+  }
+
+  /**
+   * 
+   * @param object - the instance of an XMLizable to serialize
+   * @param file where the output goes
+   * @param isFormattedOutput true for pretty printing
+   * @throws SAXException - wrapping an IOException, probably
+   */
+  public static void toJSON(XMLizable object, File file, boolean isFormattedOutput) throws SAXException {
+    try {
+      JsonGenerator jg = new JsonFactory().createGenerator(file, JsonEncoding.UTF8);
+      toJSON(object, jg, isFormattedOutput);
+    } catch (IOException e) {
+      throw new SAXException(e);
+    }
+  }
+
+}

Added: uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/impl/JsonContentHandlerJacksonWrapper.java
URL: http://svn.apache.org/viewvc/uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/impl/JsonContentHandlerJacksonWrapper.java?rev=1627840&view=auto
==============================================================================
--- uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/impl/JsonContentHandlerJacksonWrapper.java (added)
+++ uima/uimaj/trunk/uimaj-json/src/main/java/org/apache/uima/json/impl/JsonContentHandlerJacksonWrapper.java Fri Sep 26 17:41:20 2014
@@ -0,0 +1,367 @@
+/*
+ * 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.uima.json.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.util.Arrays;
+
+import javax.naming.OperationNotSupportedException;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.PrettyPrinter;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter.FixedSpaceIndenter;
+
+/**
+ * Utility class that generates JSON output for UIMA descriptors and CASs
+ * 
+ * This class is built as a wrapper for a popular open-source implementation of JSON capabilities, "Jackson".
+ * 
+ * Unusually, it implements the ContentHandler interface, so it can be use with the existing code in UIMA which uses
+ * content handlers for serialization.
+ * 
+ * Because of this, it wraps the IOExceptions that the Jackson package throws, into SAXExceptions that ContentHandlers throw.
+ * 
+ * Use: Create an instance, specifying the output as a Writer or OutputStream or File
+ *        (These three forms are supported by the underlying Jackson impl)
+ *        Specify also if doing pretty-printing
+ *      Call from other serialization class that does walking (either MetaDataObject_impl or XmiCasSerializer),
+ *        to this instance
+ *        
+ * This class is exposes the Jackson "Generator" API for streaming, and 
+ * instances of the Jackson Factory instance for configuring.
+ *        
+ * The caller uses both this class and the Jackson Generator class.
+ * 
+ * This class lets the underlying Jackson PrettyPrinter classes track the indent level.
+ * PrettyPrinting is implemented via customization of the Jackson PrettyPrinting classes
+ * 
+ */
+public class JsonContentHandlerJacksonWrapper implements ContentHandler  {
+
+  private final static char[] BLANKS = new char[80];
+  static {
+    Arrays.fill(BLANKS,  ' ');
+  }
+  
+  public final static String SYSTEM_LINE_FEED;
+  static {
+      String lf = System.getProperty("line.separator");
+      SYSTEM_LINE_FEED = (lf == null) ? "\n" : lf;
+  }
+    
+  // this value is kept here, rather than in the caller, because some callers (e.g. MetaDataObject)
+  //   have many instances (each element could be an instance).
+  private final boolean isFormattedOutput;  // set true if pretty printing
+  
+  public boolean isFormattedOutput() {
+    return isFormattedOutput;
+  }
+
+  private final JsonGenerator jg;           // the underlying Jackson Generator
+  public JsonGenerator getJsonGenerator() {
+    return jg;
+  }
+  
+  private static JsonGenerator createGenerator(JsonFactory f, Object o) throws SAXException {
+    try {
+      if (o instanceof Writer) {
+        return f.createGenerator((Writer)o);
+      }
+      if (o instanceof OutputStream) {
+        return f.createGenerator((OutputStream)o);
+      }
+      if (o instanceof File) {
+        return f.createGenerator((File)o, JsonEncoding.UTF8);
+      }
+      throw new RuntimeException(new OperationNotSupportedException(String.format("Object must be a Writer, OutputStream, or File, but was of class %s",
+          o.getClass().getName())));
+    } catch (IOException e) {
+      throw new SAXException(e);
+    }
+  }
+
+  private boolean doNl;               // a flag set by users, that does a newline at the next "significant" output 
+  private final UimaJsonPrettyPrinter uimaPrettyPrinter;  // set to null or an instance of the prettyprinter
+
+  /*
+   * Constructors
+   *   Variants:
+   *     JsonFactory - a new one created, or one supplied
+   *     isFormattedOutput - a pretty print flag, default false
+   */
+  
+  /**
+   * Makes a Json content handler that sends its output to the specified destination
+   * @param destination - can be a File, an OutputStream, or a Writer
+   * @throws SAXException wrapping an IOException
+   */
+  public JsonContentHandlerJacksonWrapper(Object destination) throws SAXException {
+    this(new JsonFactory(), destination);
+  }
+  
+  /**
+   * Makes a Json content handler, using a specified JsonFactory instance that can 
+   *   be configured according to the Jackson implementation.
+   * The resulting content handler will send its output to the specified destination
+   * @param jsonFactory -
+   * @param o - where the output goes
+   * @throws SAXException wrapping an IOException
+   */
+  public JsonContentHandlerJacksonWrapper(JsonFactory jsonFactory, Object o) throws SAXException {
+    this(createGenerator(jsonFactory, o));
+  }
+
+  /**
+   * Makes a Json content handler, and
+   *   specifies a prettyprinting boolean flag (default is no prettyprinting).
+   * The resulting content handler will send its output to the specified destination
+   * @param o - where the output goes
+   * @param isFormattedOutput -
+   * @throws SAXException wrapping an IOException
+   */
+  public JsonContentHandlerJacksonWrapper(Object o, boolean isFormattedOutput) throws SAXException {
+    this(new JsonFactory(), o, isFormattedOutput);
+  }
+  
+  /**
+   * Makes a Json content handler, using a specified JsonFactory instance that can 
+   *   be configured according to the Jackson implementation, and
+   *   specifies a prettyprinting boolean flag (default is no prettyprinting).
+   * The resulting content handler will send its output to the specified destination
+   * @param jsonFactory -
+   * @param o where the output goes
+   * @param isFormattedOutput - true for pretty printing
+   * @throws SAXException wrapping an IOException
+   */
+  public JsonContentHandlerJacksonWrapper(JsonFactory jsonFactory, Object o, boolean isFormattedOutput) throws SAXException {
+    this(
+          createGenerator((null == jsonFactory) ? new JsonFactory() : jsonFactory, o), 
+          isFormattedOutput);
+  }
+    
+  /*  C o m m o n  */
+  /**
+   * Makes a Json content handler, using a specified JsonGenerator instance
+   * @param jsonGenerator -
+   */  
+  public  JsonContentHandlerJacksonWrapper(JsonGenerator jsonGenerator) {this(jsonGenerator, false);}
+
+  /**
+   * Makes a Json content handler, using a specified JsonGenerator instance
+   * @param jsonGenerator -
+   * @param isFormattedOutput - set to true for prettyprinting, default is false
+   */  
+  public  JsonContentHandlerJacksonWrapper(JsonGenerator jsonGenerator, boolean isFormattedOutput) {
+    this.jg = jsonGenerator;
+    this.isFormattedOutput = isFormattedOutput;
+    if (isFormattedOutput) {
+      uimaPrettyPrinter = new UimaJsonPrettyPrinter();
+      uimaPrettyPrinter.withoutSpacesInObjectEntries();
+      jg.setPrettyPrinter(uimaPrettyPrinter);
+    } else {
+      uimaPrettyPrinter = null;
+    }
+  }
+
+  /**
+   * Call this to indicate that the prettyprinter should write a new line just before the next significant output.
+   *   It won't do this before a "comma", and some other punctuation.
+   * Has no effect if no prettyprinting is being done.
+   */
+  public void writeNlJustBeforeNext() {
+    doNl = true;
+  }
+  
+  /* prettyPrinting */
+    
+  private class UimaJsonPrettyPrinter extends DefaultPrettyPrinter implements PrettyPrinter {
+
+    private static final long serialVersionUID = 1L;
+    
+    /*
+     * add a new line after separators, and before array or object entries
+     * (non-Javadoc)
+     * @see com.fasterxml.jackson.core.util.DefaultPrettyPrinter#writeObjectEntrySeparator(com.fasterxml.jackson.core.JsonGenerator)
+     */
+    @Override
+    public void writeObjectEntrySeparator(JsonGenerator jg) throws IOException, JsonGenerationException {
+      super.writeObjectEntrySeparator(jg);
+      maybeOutputNlOrBlank(jg);     
+    }
+
+    @Override
+    public void writeArrayValueSeparator(JsonGenerator jg) throws IOException, JsonGenerationException {
+      super.writeArrayValueSeparator(jg);
+      maybeOutputNlOrBlank(jg);
+    }
+
+    @Override
+    public void beforeArrayValues(JsonGenerator jg) throws IOException, JsonGenerationException {
+      maybeOutputNl(jg);
+    }
+
+    @Override
+    public void beforeObjectEntries(JsonGenerator jg) throws IOException, JsonGenerationException {
+      maybeOutputNl(jg);
+    }
+    
+    private void maybeOutputNl(JsonGenerator jg) throws IOException {
+      if (doNl) {
+        maybeOutputNlIndent();
+        doNl = false;
+      }      
+    }
+    
+    private void maybeOutputNlOrBlank(JsonGenerator jg) throws IOException {
+      if (doNl) {
+        maybeOutputNl(jg);  // resets doNl
+      } else {
+        jg.writeRaw(' ');
+      }
+    }
+    
+    private void maybeOutputNlIndent () throws IOException {
+      if (isFormattedOutput) {
+        jg.writeRaw(SYSTEM_LINE_FEED);
+        jg.writeRaw(BLANKS, 0, Math.min(BLANKS.length, _nesting << 1)); // nesting * 2 for spaces per level
+      }
+    } 
+  }
+  
+  /**
+   * Assumes a Json object has been started, and adds property fields to it
+   * This method allows reusing common code in the caller.
+   * The attr values can be 
+   *   arrays (expressed as a valid Json String including the separators, used by XmiCasSerializer only 
+   *   strings (which will be scanned for needed escaping and surrounded by necessary quotes
+   *   other (which are assumed to not need surrounding quotes
+   * @param atts
+   */
+  private void outputAttrsAsProperties(Attributes atts) {
+    if (null != atts) {
+      try {        
+        for (int i = 0; i < atts.getLength(); i++) {
+          String val = atts.getValue(i);
+          if (val != null && (!val.equals(""))) {
+            final String prefix = atts.getQName(i);
+            final String attType = atts.getType(i);
+            if ("array".equals(atts.getType(i))) {
+              jg.writeArrayFieldStart(prefix);
+              jg.writeRawValue(val);  // assumes the caller has formatted the array values properly
+                                      // caller 
+              jg.writeEndArray();
+              continue;
+            }
+            
+            if ("string".equals(attType)) {
+              jg.writeStringField(prefix, val);
+              continue;
+            } 
+            
+            if ("boolean".equals(attType)) {
+              jg.writeBooleanField(prefix, val.equals("true"));
+              continue;
+            } 
+                        
+            jg.writeFieldName(prefix);
+            jg.writeRawValue(val);     
+          }
+        }
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }        
+  }
+
+  
+  /* *****************************
+   * mostly unused methods to make this 
+   * a ContentHandler
+   *******************************/
+  private void unsupported() { throw new UnsupportedOperationException();} 
+  public void characters(char[] ch, int start, int length) throws SAXException {unsupported();}
+  public void endDocument() throws SAXException {}
+  public void endPrefixMapping(String prefix) throws SAXException {}
+  public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {unsupported();}
+  public void processingInstruction(String target, String data) throws SAXException {unsupported();}
+  public void setDocumentLocator(Locator locator) {unsupported();}
+  public void skippedEntity(String name) throws SAXException {unsupported();}
+  public void startDocument() throws SAXException {}
+  public void startPrefixMapping(String prefix, String uri) throws SAXException {}
+  public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+    try {
+      if (qName != null) {
+        jg.writeStartObject();
+        jg.writeFieldName(qName);
+        jg.writeStartObject();
+      }
+      outputAttrsAsProperties(atts);
+    } catch (IOException e) {
+      throw new SAXException(e);
+    }
+  }
+  
+  public void endElement(String uri, String localName, String qName) throws SAXException {
+    try {
+    if (null != qName) {
+      jg.writeEndObject();
+    } 
+    jg.writeEndObject();
+    } catch (IOException e) {
+      throw new SAXException(e);
+    }
+  }
+
+  
+  public void withoutNl() {
+    if (isFormattedOutput) {
+      uimaPrettyPrinter.indentObjectsWith(FSIWN);
+      uimaPrettyPrinter.indentArraysWith(FSIWN);
+    }
+  }
+  
+  public void withNl() {
+    if (isFormattedOutput) {
+      uimaPrettyPrinter.indentObjectsWith(DefaultPrettyPrinter.Lf2SpacesIndenter.instance);
+    }
+  }
+  
+  private static class FixedSpaceIndenterWithNesting extends FixedSpaceIndenter {
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean isInline() { return false; }  
+  }
+  
+  private static final FixedSpaceIndenterWithNesting FSIWN = new FixedSpaceIndenterWithNesting();
+
+}