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();
+
+}