Merge branch 'main' into feature/UIMA-6266-Clean-JSON-Wire-Format-for-CAS
* main:
[UIMA-6413] Memory leak in FSClassRegistry
[UIMA-6413] Memory leak in FSClassRegistry
[UIMA-6413] Memory leak in FSClassRegistry
[UIMA-6373] Format UIMA Core Java SDK codebase
diff --git a/uimaj-core/pom.xml b/uimaj-core/pom.xml
index 845ef0a..877734a 100644
--- a/uimaj-core/pom.xml
+++ b/uimaj-core/pom.xml
@@ -242,8 +242,19 @@
<finalName>uima-core</finalName>
<pluginManagement>
<plugins>
-
- <!-- Uncomment the next to run with -Xlint:unchecked -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- Uncomment the next to run with -Xlint:unchecked -->
<!--plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/CasSerializationDeserialization_XCAS_Test.java b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/CasSerializationDeserialization_XCAS_Test.java
index faa2bc4..7a62724 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/CasSerializationDeserialization_XCAS_Test.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/CasSerializationDeserialization_XCAS_Test.java
@@ -20,7 +20,7 @@
import static java.util.Arrays.asList;
import static org.apache.uima.cas.SerialFormat.XCAS;
-import static org.apache.uima.cas.serdes.SerDesAssuptions.assumeNotKnownToFail;
+import static org.apache.uima.cas.serdes.SerDesAssumptions.assumeNotKnownToFail;
import static org.apache.uima.cas.serdes.SerDesCasIOTestUtils.createCasMaybeWithTypesystem;
import static org.apache.uima.cas.serdes.SerDesCasIOTestUtils.desser;
import static org.apache.uima.cas.serdes.SerDesCasIOTestUtils.serdes;
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/CasToComparableText.java b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/CasToComparableText.java
index 007a721..3a79a98 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/CasToComparableText.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/CasToComparableText.java
@@ -367,7 +367,9 @@
sectionHeader.add("<COVERED_TEXT>");
}
- listFeatures(aType).stream().filter(f -> !isExcluded(f)).map(f -> f.getShortName())
+ listFeatures(aType).stream() //
+ .filter(f -> !isExcluded(f)) //
+ .map(f -> f.getShortName()) //
.forEachOrdered(sectionHeader::add);
aCSV.printRecord(sectionHeader);
}
@@ -906,7 +908,12 @@
}
// Annotation? Then sort by offsets
- if (aFS1 instanceof AnnotationFS && aFS2 instanceof AnnotationFS) {
+ boolean fs1IsAnnotation = aFS1 instanceof AnnotationFS;
+ boolean fs2IsAnnotation = aFS2 instanceof AnnotationFS;
+ if (fs1IsAnnotation != fs2IsAnnotation) {
+ return -1;
+ }
+ if (fs1IsAnnotation && fs2IsAnnotation) {
AnnotationFS ann1 = (AnnotationFS) aFS1;
AnnotationFS ann2 = (AnnotationFS) aFS2;
@@ -917,7 +924,7 @@
}
// Descending by end
- int endCmp = ann1.getEnd() - ann2.getEnd();
+ int endCmp = ann2.getEnd() - ann1.getEnd();
if (endCmp != 0) {
return endCmp;
}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/PerformanceTestRunner.java b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/PerformanceTestRunner.java
new file mode 100644
index 0000000..536bfbf
--- /dev/null
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/PerformanceTestRunner.java
@@ -0,0 +1,170 @@
+/*
+ * 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.cas.serdes;
+
+import static java.lang.System.currentTimeMillis;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.annotation.Generated;
+
+import org.apache.commons.lang3.function.FailableBiConsumer;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.serdes.generators.CasGenerator;
+import org.apache.uima.resource.ResourceInitializationException;
+import org.apache.uima.resource.metadata.TypeSystemDescription;
+import org.apache.uima.util.CasCreationUtils;
+
+public class PerformanceTestRunner {
+ private final FailableBiConsumer<InputStream, CAS, Exception> deserializer;
+ private final FailableBiConsumer<CAS, OutputStream, Exception> serializer;
+ private final int iterations;
+ private final int warmUpIterations;
+
+ private CasGenerator generator;
+ private CAS randomizedCas;
+ private CAS targetCas;
+ private byte[] randomizedCasBytes;
+
+ private PerformanceTestRunner(Builder builder) {
+ this.iterations = builder.iterations;
+ this.warmUpIterations = builder.warmUpIterations;
+ this.generator = builder.generator;
+ this.serializer = builder.serializer;
+ this.deserializer = builder.deserializer;
+
+ try {
+ TypeSystemDescription tsd = generator.generateTypeSystem();
+ randomizedCas = generator.generateCas(tsd);
+ targetCas = CasCreationUtils.createCas(tsd, null, null, null);
+ } catch (ResourceInitializationException e) {
+ throw new IllegalStateException(e);
+ }
+
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+ serializer.accept(randomizedCas, bos);
+ randomizedCasBytes = bos.toByteArray();
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public void writeData(Path aPath) throws IOException {
+ Files.createDirectories(aPath.getParent());
+ try (OutputStream os = Files.newOutputStream(aPath)) {
+ os.write(randomizedCasBytes);
+ }
+ }
+
+ public int getDataSize() {
+ return randomizedCasBytes.length;
+ }
+
+ public long measureSerializationPerformance() throws Exception {
+ long total = 0;
+ for (int i = 0; i < iterations + warmUpIterations; i++) {
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+ long start = currentTimeMillis();
+ serializer.accept(randomizedCas, bos);
+ if (i >= warmUpIterations) {
+ total += currentTimeMillis() - start;
+ }
+ }
+ }
+
+ return total;
+ }
+
+ public long measureDeserializationPerformance() throws Exception {
+ long total = 0;
+ for (int i = 0; i < iterations + warmUpIterations; i++) {
+ try (ByteArrayInputStream bis = new ByteArrayInputStream(randomizedCasBytes)) {
+ long start = currentTimeMillis();
+ deserializer.accept(bis, targetCas);
+ if (i >= warmUpIterations) {
+ total += currentTimeMillis() - start;
+ }
+ }
+
+ targetCas.reset();
+ }
+
+ return total;
+ }
+
+ /**
+ * Creates builder to build {@link PerformanceTestRunner}.
+ *
+ * @return created builder
+ */
+ @Generated("SparkTools")
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder to build {@link PerformanceTestRunner}.
+ */
+ public static final class Builder {
+ private int iterations = 100;
+ private int warmUpIterations = 5;
+ private CasGenerator generator;
+ private FailableBiConsumer<InputStream, CAS, Exception> deserializer;
+ private FailableBiConsumer<CAS, OutputStream, Exception> serializer;
+
+ private Builder() {
+ }
+
+ public Builder withSerializer(FailableBiConsumer<CAS, OutputStream, Exception> aSerializer) {
+ this.serializer = aSerializer;
+ return this;
+ }
+
+ public Builder withDeserializer(FailableBiConsumer<InputStream, CAS, Exception> aDeserializer) {
+ this.deserializer = aDeserializer;
+ return this;
+ }
+
+ public Builder withIterations(int iterations) {
+ this.iterations = iterations;
+ return this;
+ }
+
+ public Builder withWarmUpIterations(int iterations) {
+ this.warmUpIterations = iterations;
+ return this;
+ }
+
+ public Builder withGenerator(CasGenerator generator) {
+ this.generator = generator;
+ return this;
+ }
+
+ public PerformanceTestRunner build() {
+ return new PerformanceTestRunner(this);
+ }
+ }
+
+}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/Performance_SerialFormat_Test.java b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/Performance_SerialFormat_Test.java
new file mode 100644
index 0000000..fa3b717
--- /dev/null
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/Performance_SerialFormat_Test.java
@@ -0,0 +1,68 @@
+/*
+ * 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.cas.serdes;
+
+import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;
+
+import java.util.Random;
+
+import org.apache.uima.cas.SerialFormat;
+import org.apache.uima.cas.serdes.generators.MultiFeatureRandomCasGenerator;
+import org.apache.uima.util.CasIOUtils;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+public class Performance_SerialFormat_Test {
+
+ private static final int ITERATIONS = 100;
+ private static final int SIZE = 1_000;
+
+ @ParameterizedTest
+ @EnumSource(value = SerialFormat.class, mode = EXCLUDE, names = { "UNKNOWN",
+ "COMPRESSED_PROJECTION" })
+ public void jsonSerialization(SerialFormat aFormat) throws Exception {
+ PerformanceTestRunner runner = buildRunner(aFormat);
+ long serDuration = runner.measureSerializationPerformance();
+
+ System.out.printf("[%23s] %d CASes with %d feature structures (%7d bytes each)%n", aFormat,
+ ITERATIONS, SIZE, runner.getDataSize());
+
+ System.out.printf("[%23s] %6s ms serialization %6.2f fs/sec %6.2f CAS/sec %n", aFormat,
+ serDuration, (ITERATIONS * SIZE) / (serDuration / 1000.0d),
+ ITERATIONS / (serDuration / 1000.0d));
+
+ long desDuration = runner.measureDeserializationPerformance();
+
+ System.out.printf("[%23s] %6s ms deserialization %6.2f fs/sec %6.2f CAS/sec %n", aFormat,
+ desDuration, (ITERATIONS * SIZE) / (desDuration / 1000.0d),
+ ITERATIONS / (desDuration / 1000.0d));
+ }
+
+ public PerformanceTestRunner buildRunner(SerialFormat aFormat) throws Exception {
+ return PerformanceTestRunner.builder() //
+ .withIterations(ITERATIONS) //
+ .withDeserializer(CasIOUtils::load)
+ .withSerializer((cas, os) -> CasIOUtils.save(cas, os, aFormat)) //
+ .withGenerator(MultiFeatureRandomCasGenerator.builder() //
+ .withRandomGenerator(new Random(123456l)) //
+ .withSize(SIZE) //
+ .build()) //
+ .build();
+ }
+}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssumptions.java
similarity index 97%
rename from uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
rename to uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssumptions.java
index 6ba69f5..6f25a52 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssumptions.java
@@ -22,7 +22,7 @@
import java.util.regex.Pattern;
-public class SerDesAssuptions {
+public class SerDesAssumptions {
public static void assumeNotKnownToFail(Runnable aScenario, String... aPatternsAndReasons) {
for (int i = 0; i < aPatternsAndReasons.length; i += 2) {
String pattern = aPatternsAndReasons[i];
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/generators/MultiFeatureRandomCasGenerator.java b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/generators/MultiFeatureRandomCasGenerator.java
index c171bb3..5e47095 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/generators/MultiFeatureRandomCasGenerator.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/generators/MultiFeatureRandomCasGenerator.java
@@ -86,9 +86,11 @@
private static final short[] SHORT_VALUES = { 1, 0, -1, Short.MAX_VALUE, Short.MIN_VALUE, 22,
-22 };
private static final double[] DOUBLE_VALUES = { 1d, 0d, -1d, Double.MAX_VALUE,
- /* Double.MIN_NORMAL, */ Double.MIN_VALUE, 33d, -33.33d };
+ /* Double.MIN_NORMAL, */ Double.MIN_VALUE, 33d, -33.33d, Double.NaN, Double.NEGATIVE_INFINITY,
+ Double.POSITIVE_INFINITY };
private static final float[] FLOAT_VALUES = { 1f, 0f, -1f, Float.MAX_VALUE,
- /* Float.MIN_NORMAL, */ Float.MIN_VALUE, 17f, -22.33f };
+ /* Float.MIN_NORMAL, */ Float.MIN_VALUE, 17f, -22.33f, Float.NaN, Float.NEGATIVE_INFINITY,
+ Float.POSITIVE_INFINITY };
/**
* set to true to change FS creation to keep references to all created FS.
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/scenario/SerDesTestScenario.java b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/scenario/SerDesTestScenario.java
index 02bb063..bf0b951 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/scenario/SerDesTestScenario.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/scenario/SerDesTestScenario.java
@@ -18,6 +18,7 @@
*/
package org.apache.uima.cas.serdes.scenario;
+import static org.apache.uima.cas.impl.CasCompare.compareCASes;
import static org.apache.uima.cas.serdes.CasToComparableText.toComparableString;
import static org.assertj.core.api.Assertions.assertThat;
@@ -25,7 +26,6 @@
import org.apache.commons.lang3.function.FailableSupplier;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.impl.CASImpl;
-import org.apache.uima.cas.impl.CasCompare;
import org.apache.uima.cas.serdes.transitions.CasSerDesCycleConfiguration;
import org.apache.uima.cas.serdes.transitions.CasSourceTargetConfiguration;
import org.assertj.core.internal.Failures;
@@ -91,7 +91,7 @@
assertThat(targetCas) //
.as("CasCompare comparison must match (%s)", debugInfo)
- .usingComparator((a, b) -> CasCompare.compareCASes((CASImpl) a, (CASImpl) b) ? 0 : 1) //
+ .usingComparator((a, b) -> compareCASes((CASImpl) a, (CASImpl) b) ? 0 : 1) //
.isEqualTo(sourceCas);
}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/scenario/SerRefTestScenario.java b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/scenario/SerRefTestScenario.java
index fc6d15b..9d99a9c 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/scenario/SerRefTestScenario.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/serdes/scenario/SerRefTestScenario.java
@@ -22,6 +22,7 @@
import static org.apache.uima.cas.serdes.SerDesCasIOTestUtils.writeXmi;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.contentOf;
+import static org.assertj.core.api.Assumptions.assumeThat;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -98,7 +99,7 @@
writeTypeSystemDescription(sourceCas, targetCasFile.resolveSibling("debug-typesystem.xml"));
// Compare the serialized CAS file against the reference
- assertThat(referenceCasFile.toFile()) //
+ assumeThat(referenceCasFile.toFile()) //
.as("Reference file must exists at %s", referenceCasFile) //
.exists();
assertThat(contentOf(targetCasFile.toFile())) //
diff --git a/uimaj-json/pom.xml b/uimaj-json/pom.xml
index eef3abd..7bb20a3 100644
--- a/uimaj-json/pom.xml
+++ b/uimaj-json/pom.xml
@@ -17,7 +17,9 @@
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">
+<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>
@@ -28,70 +30,142 @@
<artifactId>uimaj-json</artifactId>
<name>Apache UIMA Base: ${project.artifactId}: JSON</name>
<description>JSON support for UIMA SDK</description>
-
+
<scm>
<connection>scm:git:https://github.com/apache/uima-uimaj/</connection>
<developerConnection>scm:git:https://github.com/apache/uima-uimaj/</developerConnection>
<url>https://github.com/apache/uima-uimaj/</url>
<tag>uimaj-3.2.0</tag>
</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>${jackson.version}</version>
</dependency>
-
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.apache.uima</groupId>
<artifactId>uimaj-test-util</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
-
+ <dependency>
+ <groupId>org.apache.uima</groupId>
+ <artifactId>uimaj-core</artifactId>
+ <version>${project.parent.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.skyscreamer</groupId>
+ <artifactId>jsonassert</artifactId>
+ <version>1.5.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.javacrumbs.json-unit</groupId>
+ <artifactId>json-unit-assertj</artifactId>
+ <version>2.28.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.javacrumbs.json-unit</groupId>
+ <artifactId>json-unit-json-path</artifactId>
+ <version>2.28.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.12.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.7</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-csv</artifactId>
+ <version>1.8</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.uima</groupId>
+ <artifactId>uimaj-core</artifactId>
+ <version>${project.parent.version}</version>
+ <classifier>tests</classifier>
+ <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 combine.children="append">
- <exclude>src/test/resources/CasSerialization/expected/json/*.txt</exclude>
- <exclude>src/test/resources/CasSerialization/expected/xmi/*.xml</exclude>
- <exclude>src/test/resources/CASTests/json/expected/*.json</exclude>
- <exclude>src/test/java/org/apache/uima/test/*.java</exclude>
- </excludes>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-cli</id>
+ <configuration>
+ <excludes combine.children="append">
+ <exclude>src/test/resources/**/data.json</exclude>
+ <exclude>src/test/resources/**/debug.xmi</exclude>
+ <exclude>src/test/resources/**/debug-typesystem.xml</exclude>
+ <exclude>src/test/resources/FlexJsonDeserializer/**/*.json</exclude>
+ <exclude>src/test/resources/FlexJsonSerializerTest/**/*.json</exclude>
+ <exclude>src/test/resources/CasSerialization/expected/xmi/*.xml</exclude>
+ <exclude>src/test/resources/CasSerialization/expected/json/*.txt</exclude>
+ <exclude>src/test/resources/CASTests/json/expected/*.json</exclude>
+ <exclude>src/test/java/org/apache/uima/test/*.java</exclude>
+ </excludes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
</pluginManagement>
-
- <plugins>
- </plugins>
</build>
-
</project>
\ No newline at end of file
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/flexjson/FlexJsonCasDeserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/FlexJsonCasDeserializer.java
new file mode 100644
index 0000000..f0d5e56
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/FlexJsonCasDeserializer.java
@@ -0,0 +1,241 @@
+/*
+ * 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.flexjson;
+
+import static com.fasterxml.jackson.core.JsonTokenId.ID_END_ARRAY;
+import static com.fasterxml.jackson.core.JsonTokenId.ID_END_OBJECT;
+import static com.fasterxml.jackson.core.JsonTokenId.ID_START_ARRAY;
+import static com.fasterxml.jackson.core.JsonTokenId.ID_START_OBJECT;
+import static com.fasterxml.jackson.core.JsonTokenId.ID_STRING;
+import static org.apache.uima.cas.CAS.FEATURE_BASE_NAME_LANGUAGE;
+import static org.apache.uima.cas.CAS.FEATURE_BASE_NAME_SOFAID;
+import static org.apache.uima.cas.CAS.FEATURE_BASE_NAME_SOFASTRING;
+import static org.apache.uima.cas.CAS.TYPE_NAME_INTEGER;
+import static org.apache.uima.cas.CAS.TYPE_NAME_SOFA;
+import static org.apache.uima.cas.CAS.TYPE_NAME_STRING;
+import static org.apache.uima.json.flexjson.FlexJsonNames.FEATURE_STRUCTURES_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.FLAG_DOCUMENT_ANNOTATION;
+import static org.apache.uima.json.flexjson.FlexJsonNames.TYPES_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.VIEWS_FIELD;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Feature;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.Type;
+import org.apache.uima.json.flexjson.model.Json2FeatureStructure;
+import org.apache.uima.json.flexjson.model.Json2Type;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+@Deprecated
+public class FlexJsonCasDeserializer {
+ private final JsonParser parser;
+
+ private Map<String, Json2Type> types;
+
+ private Map<String, CAS> views;
+
+ public FlexJsonCasDeserializer(JsonParser aParser) {
+ parser = aParser;
+ }
+
+ public void read(CAS aCas) throws IOException {
+ views = new HashMap<>();
+
+ aCas.getViewIterator().forEachRemaining(view -> views.put(view.getViewName(), view));
+
+ boolean isFirst = true;
+
+ Map<String, Json2FeatureStructure> jfses = null;
+
+ while (parser.nextToken() != null) {
+ if (isFirst) {
+ isFirst = false;
+ switch (parser.currentTokenId()) {
+ case ID_STRING:
+ readDocumentText(aCas, parser.getValueAsString());
+ return;
+ case ID_START_ARRAY:
+ jfses = readFeatureStructuresAsArray(parser);
+ return;
+ case ID_START_OBJECT:
+ // In this case, we continue in the main loop and process the type system and
+ // feature structures if we find them.
+ parser.nextFieldName();
+ break;
+ default:
+ throw new IOException("JSON must start with an object, array or string value");
+ }
+ }
+
+ if (parser.currentTokenId() == ID_END_ARRAY || parser.currentTokenId() == ID_END_OBJECT) {
+ break;
+ }
+
+ // If we get here, we are operating on an object-type representation of the full CAS
+ switch (parser.getValueAsString()) {
+ case TYPES_FIELD:
+ parser.nextValue();
+ types = readTypeSystem(parser);
+ break;
+ case VIEWS_FIELD:
+ readViews(aCas, parser);
+ break;
+ case FEATURE_STRUCTURES_FIELD:
+ switch (parser.nextToken().id()) {
+ case ID_START_ARRAY:
+ jfses = readFeatureStructuresAsArray(parser);
+ break;
+ case ID_START_OBJECT:
+ jfses = readFeatureStructuresAsObject(parser);
+ break;
+ default:
+ throw new IOException("Feature structures must be an object or array");
+ }
+ break;
+ }
+ }
+
+ if (jfses != null) {
+ for (Json2FeatureStructure jfs : jfses.values()) {
+ createFeatureStructure(aCas, jfs);
+ }
+ }
+ }
+
+ private void createFeatureStructure(CAS aCas, Json2FeatureStructure aJfs) {
+ Type type = aCas.getTypeSystem().getType(aJfs.getType());
+
+ if (TYPE_NAME_SOFA.equals(type.getName())) {
+ CAS view = getView(aCas, (String) aJfs.getFeatures().get(FEATURE_BASE_NAME_SOFAID));
+ view.setDocumentText((String) aJfs.getFeatures().get(FEATURE_BASE_NAME_SOFASTRING));
+ view.setDocumentLanguage((String) aJfs.getFeatures().get(FEATURE_BASE_NAME_LANGUAGE));
+ return;
+ }
+
+ FeatureStructure fs = getFSCreationView(aCas, aJfs).createFS(type);
+
+ if (aJfs.getFlags() != null && aJfs.getFlags().contains(FLAG_DOCUMENT_ANNOTATION)) {
+ fs.getCAS().removeFsFromIndexes(fs.getCAS().getDocumentAnnotation());
+ }
+
+ for (Entry<String, Object> entry : aJfs.getFeatures().entrySet()) {
+ Feature feature = type.getFeatureByBaseName(entry.getKey());
+ if (feature.getRange().isPrimitive()) {
+ switch (feature.getRange().getName()) {
+ case TYPE_NAME_STRING:
+ fs.setStringValue(feature, (String) entry.getValue());
+ break;
+ case TYPE_NAME_INTEGER:
+ fs.setIntValue(feature, (Integer) entry.getValue());
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported primitive type [" + feature.getRange().getName() + "]");
+ }
+ }
+ }
+
+ addToIndexes(fs, aJfs);
+ }
+
+ private CAS getFSCreationView(CAS aCas, Json2FeatureStructure aJfs) {
+ if (aJfs.getViews().isEmpty()) {
+ return null;
+ }
+
+ if (aJfs.getViews() != null) {
+ return getView(aCas, aJfs.getViews().iterator().next());
+ }
+
+ // FIXME: case when the views are not inline but separate or when FS is simply
+ // not indexed
+ throw new UnsupportedOperationException();
+ }
+
+ private CAS getView(CAS aCas, String aViewName) {
+ CAS view = views.get(aViewName);
+ if (view == null) {
+ view = aCas.createView(aViewName);
+ views.put(aViewName, view);
+ }
+ return view;
+ }
+
+ private void addToIndexes(FeatureStructure aFS, Json2FeatureStructure aJfs) {
+ if (aJfs.getViews() != null) {
+ for (String viewName : aJfs.getViews()) {
+ getView(aFS.getCAS(), viewName).addFsToIndexes(aFS);
+ }
+ }
+ }
+
+ private void readViews(CAS aCas, JsonParser aParser) throws IOException {
+ while (aParser.currentTokenId() != ID_END_OBJECT) {
+ aParser.nextFieldName();
+ String viewName = aParser.readValueAs(String.class);
+ aCas.createView(viewName);
+ }
+ }
+
+ private Map<String, Json2Type> readTypeSystem(JsonParser aParser) throws IOException {
+ return aParser.readValueAs(TYPES_MAP_TYPE_REF);
+ }
+
+ private Map<String, Json2FeatureStructure> readFeatureStructuresAsObject(JsonParser aParser)
+ throws IOException {
+ Map<String, Json2FeatureStructure> jfses = new LinkedHashMap<>();
+ while (aParser.currentTokenId() != ID_END_OBJECT) {
+ String id = aParser.nextFieldName();
+ Json2FeatureStructure jfs = aParser.readValueAs(Json2FeatureStructure.class);
+ jfs.setId(id);
+ jfses.put(id, jfs);
+ }
+
+ return jfses;
+ }
+
+ private Map<String, Json2FeatureStructure> readFeatureStructuresAsArray(JsonParser aParser)
+ throws IOException {
+ Map<String, Json2FeatureStructure> jfses = new LinkedHashMap<>();
+ parser.nextValue();
+ while (aParser.currentTokenId() != ID_END_ARRAY) {
+ Json2FeatureStructure jfs = aParser.readValueAs(Json2FeatureStructure.class);
+ aParser.nextToken();
+ jfses.put(jfs.getId(), jfs);
+ }
+
+ return jfses;
+ }
+
+ private void readDocumentText(CAS aCas, String aString) {
+ aCas.setDocumentText(aString);
+ }
+
+ public static final TypeReference<LinkedHashMap<String, Json2Type>> TYPES_MAP_TYPE_REF = //
+ new TypeReference<LinkedHashMap<String, Json2Type>>() {
+ };
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/flexjson/FlexJsonCasSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/FlexJsonCasSerializer.java
new file mode 100644
index 0000000..8e52262
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/FlexJsonCasSerializer.java
@@ -0,0 +1,455 @@
+/*
+ * 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.flexjson;
+
+import static com.fasterxml.jackson.core.JsonEncoding.UTF8;
+import static java.util.Arrays.asList;
+import static java.util.Arrays.sort;
+import static java.util.Collections.sort;
+import static java.util.Collections.unmodifiableSet;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+import static org.apache.uima.json.flexjson.FlexJsonCasSerializer.FeatureStructuresMode.AS_ARRAY;
+import static org.apache.uima.json.flexjson.FlexJsonCasSerializer.ViewsMode.INLINE;
+import static org.apache.uima.json.flexjson.FlexJsonCasSerializer.ViewsMode.SEPARATE;
+import static org.apache.uima.json.flexjson.FlexJsonNames.COMPONENT_TYPE_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.FEATURE_STRUCTURES_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.FLAG_DOCUMENT_ANNOTATION;
+import static org.apache.uima.json.flexjson.FlexJsonNames.ID_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.REF;
+import static org.apache.uima.json.flexjson.FlexJsonNames.SUPER_TYPE_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.TYPES_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.TYPE_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.VIEWS_FIELD;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.StreamSupport;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Feature;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.Type;
+import org.apache.uima.cas.TypeSystem;
+import org.apache.uima.cas.impl.CASImpl;
+import org.apache.uima.jcas.cas.TOP;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@Deprecated
+public class FlexJsonCasSerializer {
+ public enum FeatureStructuresMode {
+ AS_OBJECT, AS_ARRAY
+ }
+
+ public enum ViewsMode {
+ SEPARATE, INLINE
+ }
+
+ private final Set<String> BUILT_IN_TYPES = unmodifiableSet(new HashSet<>(asList(
+ CAS.TYPE_NAME_ANNOTATION, CAS.TYPE_NAME_ANNOTATION_BASE, CAS.TYPE_NAME_ARRAY_BASE,
+ CAS.TYPE_NAME_BOOLEAN, CAS.TYPE_NAME_BOOLEAN_ARRAY, CAS.TYPE_NAME_BYTE,
+ CAS.TYPE_NAME_BYTE_ARRAY, CAS.TYPE_NAME_DOCUMENT_ANNOTATION, CAS.TYPE_NAME_DOUBLE,
+ CAS.TYPE_NAME_DOUBLE_ARRAY, CAS.TYPE_NAME_EMPTY_FLOAT_LIST, CAS.TYPE_NAME_EMPTY_FS_LIST,
+ CAS.TYPE_NAME_EMPTY_INTEGER_LIST, CAS.TYPE_NAME_EMPTY_STRING_LIST, CAS.TYPE_NAME_FLOAT,
+ CAS.TYPE_NAME_FLOAT_ARRAY, CAS.TYPE_NAME_FLOAT_LIST, CAS.TYPE_NAME_FS_ARRAY,
+ CAS.TYPE_NAME_FS_LIST, CAS.TYPE_NAME_INTEGER, CAS.TYPE_NAME_INTEGER_ARRAY,
+ CAS.TYPE_NAME_INTEGER_LIST, CAS.TYPE_NAME_LIST_BASE, CAS.TYPE_NAME_LONG,
+ CAS.TYPE_NAME_LONG_ARRAY, CAS.TYPE_NAME_NON_EMPTY_FLOAT_LIST,
+ CAS.TYPE_NAME_NON_EMPTY_FS_LIST, CAS.TYPE_NAME_NON_EMPTY_INTEGER_LIST,
+ CAS.TYPE_NAME_NON_EMPTY_STRING_LIST, CAS.TYPE_NAME_SHORT, CAS.TYPE_NAME_SHORT_ARRAY,
+ CAS.TYPE_NAME_SOFA, CAS.TYPE_NAME_STRING, CAS.TYPE_NAME_STRING_ARRAY,
+ CAS.TYPE_NAME_STRING_LIST, CAS.TYPE_NAME_TOP)));
+
+ private JsonGenerator jg;
+
+ private FeatureStructuresMode featureStructuresMode = AS_ARRAY;
+
+ private ViewsMode viewsMode = INLINE;
+
+ private Supplier<Function<FeatureStructure, String>> idRefGeneratorSupplier;
+ private Function<FeatureStructure, String> idRefGenerator;
+ private Map<FeatureStructure, String> idRefCache;
+
+ private Supplier<Function<Type, String>> typeRefGeneratorSupplier;
+ private Function<Type, String> typeRefGenerator;
+ private Map<Type, String> typeRefCache;
+
+ private Map<FeatureStructure, Set<String>> fsToViewsCache;
+
+ public FlexJsonCasSerializer(JsonGenerator aJg) {
+ jg = aJg;
+ setIdRefGeneratorSupplier(SequentialIdRefGenerator::new);
+ setTypeRefGeneratorSupplier(FullyQualifiedTypeRefGenerator::new);
+ }
+
+ public void setViewsMode(ViewsMode aViewsMode) {
+ viewsMode = aViewsMode;
+ }
+
+ public ViewsMode getViewsMode() {
+ return viewsMode;
+ }
+
+ public FeatureStructuresMode getFeatureStructuresMode() {
+ return featureStructuresMode;
+ }
+
+ public void setFeatureStructuresMode(FeatureStructuresMode aFeatureStructuresMode) {
+ featureStructuresMode = aFeatureStructuresMode;
+ }
+
+ public void setIdRefGeneratorSupplier(
+ Supplier<Function<FeatureStructure, String>> aIdRefGeneratorSupplier) {
+ idRefGeneratorSupplier = aIdRefGeneratorSupplier;
+ }
+
+ public void setTypeRefGeneratorSupplier(
+ Supplier<Function<Type, String>> aTypeRefGeneratorSupplier) {
+ typeRefGeneratorSupplier = aTypeRefGeneratorSupplier;
+ }
+
+ public void write(CAS aCas) throws IOException {
+ idRefGenerator = idRefGeneratorSupplier.get();
+ typeRefGenerator = typeRefGeneratorSupplier.get();
+
+ idRefCache = new HashMap<>();
+ typeRefCache = new HashMap<>();
+ fsToViewsCache = new IdentityHashMap<>();
+
+ jg.writeStartObject(aCas);
+
+ writeTypeSystem(aCas.getTypeSystem());
+
+ List<CAS> views = new ArrayList<>();
+ aCas.getViewIterator().forEachRemaining(views::add);
+ if (!views.isEmpty()) {
+ sort(views, comparing(CAS::getViewName));
+
+ if (viewsMode == SEPARATE) {
+ jg.writeObjectFieldStart(VIEWS_FIELD);
+ for (CAS view : views) {
+ jg.writeFieldName(view.getViewName());
+ writeView(view);
+ }
+ jg.writeEndObject();
+ }
+
+ if (viewsMode == INLINE) {
+ for (CAS view : views) {
+ for (FeatureStructure fs : view.select()) {
+ fsToViewsCache.computeIfAbsent(fs, _fs -> new HashSet<>()).add(view.getViewName());
+ }
+ }
+ }
+ }
+
+ Set<FeatureStructure> allFSes = findAllFeatureStructures(aCas);
+
+ if (!allFSes.isEmpty()) {
+ switch (featureStructuresMode) {
+ case AS_ARRAY:
+ jg.writeArrayFieldStart(FEATURE_STRUCTURES_FIELD);
+ for (FeatureStructure fs : allFSes) {
+ jg.writeStartObject(fs);
+ jg.writeStringField(ID_FIELD, fsRef(fs));
+ writeFeatureStructure(fs);
+ jg.writeEndObject();
+ }
+ jg.writeEndArray();
+ break;
+ case AS_OBJECT:
+ jg.writeObjectFieldStart(FEATURE_STRUCTURES_FIELD);
+ for (FeatureStructure fs : allFSes) {
+ jg.writeFieldName(fsRef(fs));
+ jg.writeStartObject(fs);
+ writeFeatureStructure(fs);
+ jg.writeEndObject();
+ }
+ jg.writeEndObject();
+ break;
+ default:
+ throw new IOException("Unsupported feature structures serialization mode: ["
+ + featureStructuresMode + "]");
+ }
+ }
+
+ jg.writeEndObject();
+ }
+
+ private Set<FeatureStructure> findAllFeatureStructures(CAS aCas) {
+ Set<FeatureStructure> allFSes = new LinkedHashSet<>();
+ ((CASImpl) aCas).walkReachablePlusFSsSorted(allFSes::add, null, null, null);
+ return allFSes;
+ }
+
+ private String fsRef(FeatureStructure aFs) {
+ return idRefCache.computeIfAbsent(aFs, idRefGenerator);
+ }
+
+ private String typeRef(Type aType) {
+ return typeRefCache.computeIfAbsent(aType, typeRefGenerator);
+ }
+
+ private void writeFeatureStructure(FeatureStructure aFs) throws IOException {
+ Type type = aFs.getType();
+ jg.writeStringField(TYPE_FIELD, typeRef(type));
+
+ if (viewsMode == INLINE) {
+ Set<String> views = fsToViewsCache.get(aFs);
+
+ if (views != null && !views.isEmpty()) {
+ String[] viewsArray = views.toArray(new String[views.size()]);
+ sort(viewsArray);
+ jg.writeArrayFieldStart(VIEWS_FIELD);
+ for (String view : viewsArray) {
+ jg.writeString(view);
+ }
+ jg.writeEndArray();
+ }
+ }
+
+ List<String> flags = new ArrayList<>();
+ if (((CASImpl) aFs.getCAS()).getDocumentAnnotationNoCreate() == aFs) {
+ flags.add(FLAG_DOCUMENT_ANNOTATION);
+ }
+
+ if (!flags.isEmpty()) {
+ jg.writeArrayFieldStart(FlexJsonNames.FLAGS_FIELD);
+ for (String flag : flags) {
+ jg.writeString(flag);
+ }
+ jg.writeEndArray();
+ }
+
+ for (Feature feature : type.getFeatures()) {
+ writeFeature(aFs, feature);
+ }
+ }
+
+ private void writeFeature(FeatureStructure aFs, Feature aFeature) throws IOException {
+ if (!aFeature.getRange().isPrimitive()) {
+ FeatureStructure target = aFs.getFeatureValue(aFeature);
+ if (target != null) {
+ jg.writeFieldName(REF + aFeature.getShortName());
+ jg.writeString(fsRef(aFs.getFeatureValue(aFeature)));
+ }
+ return;
+ }
+
+ if (aFeature.getRange().isStringOrStringSubtype()) {
+ String value = aFs.getStringValue(aFeature);
+ if (value != null) {
+ jg.writeFieldName(aFeature.getShortName());
+ jg.writeString(value);
+ }
+
+ return;
+ }
+
+ jg.writeFieldName(aFeature.getShortName());
+ String rangeTypeName = aFeature.getRange().getName();
+ switch (rangeTypeName) {
+ case CAS.TYPE_NAME_INTEGER:
+ jg.writeNumber(aFs.getIntValue(aFeature));
+ break;
+ case CAS.TYPE_NAME_BOOLEAN:
+ jg.writeBoolean(aFs.getBooleanValue(aFeature));
+ break;
+ default:
+ throw new IOException("Unsupported primitive type [" + rangeTypeName + "]");
+ }
+ }
+
+ private void writeView(CAS aView) throws IOException {
+ jg.writeStartArray();
+
+ jg.writeString(fsRef(aView.getSofa()));
+ for (TOP fs : aView.getIndexedFSs()) {
+ jg.writeString(fsRef(fs));
+ }
+
+ jg.writeEndArray();
+ }
+
+ private void writeTypeSystem(TypeSystem aTypeSystem) throws IOException {
+ List<Type> types = StreamSupport.stream(aTypeSystem.spliterator(), false)
+ .sorted(comparing(Type::getName))
+ .filter(type -> !BUILT_IN_TYPES.contains(type.getName())).collect(toList());
+
+ if (types.isEmpty()) {
+ return;
+ }
+
+ jg.writeFieldName(TYPES_FIELD);
+
+ jg.writeStartObject(aTypeSystem);
+
+ for (Type type : types) {
+ writeType(aTypeSystem, type);
+ }
+
+ jg.writeEndObject();
+ }
+
+ private void writeType(TypeSystem aTypeSystem, Type aType) throws IOException {
+ jg.writeFieldName(typeRef(aType));
+
+ jg.writeStartObject(aType);
+
+ if (!typeRef(aType).equals(aType.getName())) {
+ jg.writeStringField(FlexJsonNames.NAME_FIELD, aType.getName());
+ }
+
+ Type parent = aTypeSystem.getParent(aType);
+ if (parent != null) {
+ jg.writeStringField(SUPER_TYPE_FIELD, parent.getName());
+ }
+
+ if (aType.getComponentType() != null) {
+ jg.writeStringField(COMPONENT_TYPE_FIELD, aType.getComponentType().getName());
+ }
+
+ List<Feature> newFeatures = aType.getFeatures().stream().filter(f -> f.getDomain() == aType)
+ .collect(toList());
+ if (!newFeatures.isEmpty()) {
+ for (Feature feature : newFeatures) {
+ jg.writeStringField(feature.getShortName(), feature.getRange().getName());
+ }
+ }
+
+ jg.writeEndObject();
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Supplier<Function<FeatureStructure, String>> idRefGeneratorSupplier;
+ private Supplier<Function<Type, String>> typeRefGeneratorSupplier;
+ private FeatureStructuresMode featureStructuresMode = AS_ARRAY;
+ private ViewsMode viewsMode = INLINE;
+
+ public Builder() {
+ setIdRefGeneratorSupplier(SequentialIdRefGenerator::new);
+ setTypeRefGeneratorSupplier(FullyQualifiedTypeRefGenerator::new);
+ }
+
+ public Builder setFeatureStructuresMode(FeatureStructuresMode aFeatureStructuresMode) {
+ featureStructuresMode = aFeatureStructuresMode;
+ return this;
+ }
+
+ public Builder setViewsMode(ViewsMode aViewsMode) {
+ viewsMode = aViewsMode;
+ return this;
+ }
+
+ public Builder setIdRefGeneratorSupplier(
+ Supplier<Function<FeatureStructure, String>> aIdRefGeneratorSupplier) {
+ idRefGeneratorSupplier = aIdRefGeneratorSupplier;
+ return this;
+ }
+
+ public Builder setTypeRefGeneratorSupplier(
+ Supplier<Function<Type, String>> aTypeRefGeneratorSupplier) {
+ typeRefGeneratorSupplier = aTypeRefGeneratorSupplier;
+ return this;
+ }
+
+ public FlexJsonCasSerializer build(JsonGenerator jg) {
+ FlexJsonCasSerializer ser = new FlexJsonCasSerializer(jg);
+ ser.setFeatureStructuresMode(featureStructuresMode);
+ ser.setViewsMode(viewsMode);
+ ser.setIdRefGeneratorSupplier(idRefGeneratorSupplier);
+ ser.setTypeRefGeneratorSupplier(typeRefGeneratorSupplier);
+ return ser;
+ }
+
+ public void write(CAS aCas, File aTargetFile) throws IOException {
+ JsonFactory jsonFactory = new JsonFactory();
+ jsonFactory.setCodec(new ObjectMapper());
+ try (JsonGenerator jg = jsonFactory.createGenerator(aTargetFile, UTF8)
+ .useDefaultPrettyPrinter()) {
+ FlexJsonCasSerializer ser = build(jg);
+ ser.write(aCas);
+ }
+ }
+ }
+
+ public static void write(CAS aCas, File aTargetFile) throws IOException {
+ JsonFactory jsonFactory = new JsonFactory();
+ jsonFactory.setCodec(new ObjectMapper());
+ try (JsonGenerator jg = jsonFactory.createGenerator(aTargetFile, UTF8)
+ .useDefaultPrettyPrinter()) {
+ FlexJsonCasSerializer ser = new FlexJsonCasSerializer(jg);
+ ser.write(aCas);
+ }
+ }
+
+ public static class SequentialIdRefGenerator implements Function<FeatureStructure, String> {
+ private int nextId = 0;
+
+ @Override
+ public String apply(FeatureStructure aT) {
+ return String.valueOf(nextId++);
+ }
+ }
+
+ public static class FullyQualifiedTypeRefGenerator implements Function<Type, String> {
+ @Override
+ public String apply(Type aType) {
+ return aType.getName();
+ }
+ }
+
+ public static class ShortTypeRefGenerator implements Function<Type, String> {
+ private Set<String> usedNames = new HashSet<>();
+
+ @Override
+ public String apply(Type aType) {
+ if (!usedNames.contains(aType.getShortName())) {
+ usedNames.add(aType.getShortName());
+ return aType.getShortName();
+ }
+
+ int n = 1;
+ String newName;
+ while (usedNames.contains(newName = aType.getShortName() + "-" + n)) {
+ n++;
+ }
+
+ usedNames.add(newName);
+ return newName;
+ }
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/flexjson/FlexJsonNames.java b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/FlexJsonNames.java
new file mode 100644
index 0000000..2590836
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/FlexJsonNames.java
@@ -0,0 +1,48 @@
+/*
+ * 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.flexjson;
+
+@Deprecated
+public class FlexJsonNames {
+ private static final String SPECIAL = "%";
+
+ public static final String TYPE_FIELD = SPECIAL + "TYPE";
+
+ public static final String TYPES_FIELD = SPECIAL + "TYPES";
+
+ public static final String FEATURES_FIELD = SPECIAL + "FEATURES";
+
+ public static final String VIEWS_FIELD = SPECIAL + "VIEWS";
+
+ public static final String FEATURE_STRUCTURES_FIELD = SPECIAL + "FEATURE_STRUCTURES";
+
+ public static final String REF = "@";
+
+ public static final String NAME_FIELD = SPECIAL + "NAME";
+
+ public static final String SUPER_TYPE_FIELD = SPECIAL + "SUPER_TYPE";
+
+ public static final String COMPONENT_TYPE_FIELD = SPECIAL + "COMPONENT_TYPE";
+
+ public static final String ID_FIELD = SPECIAL + "ID";
+
+ public static final String FLAGS_FIELD = SPECIAL + "FLAGS";
+
+ public static final String FLAG_DOCUMENT_ANNOTATION = "DocumentAnnotation";
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/flexjson/model/Json2FeatureStructure.java b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/model/Json2FeatureStructure.java
new file mode 100644
index 0000000..7b3ab19
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/model/Json2FeatureStructure.java
@@ -0,0 +1,102 @@
+/*
+ * 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.flexjson.model;
+
+import static org.apache.uima.cas.CAS.TYPE_NAME_ANNOTATION;
+import static org.apache.uima.json.flexjson.FlexJsonNames.FLAGS_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.ID_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.TYPE_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.VIEWS_FIELD;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+
+import org.apache.uima.json.jsoncas2.JsonCas2Names;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+@Deprecated
+@JsonPropertyOrder(value = { "id", "type", "views" })
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Json2FeatureStructure {
+ @JsonProperty(value = ID_FIELD, required = false)
+ private String id;
+
+ @JsonProperty(value = TYPE_FIELD, required = true, defaultValue = TYPE_NAME_ANNOTATION)
+ private String type;
+
+ @JsonProperty(value = FLAGS_FIELD, required = false)
+ private LinkedHashSet<String> flags;
+
+ @JsonProperty(value = VIEWS_FIELD, required = false)
+ private LinkedHashSet<String> views;
+
+ private Map<String, Object> features = new LinkedHashMap<>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String aId) {
+ id = aId;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String aType) {
+ type = aType;
+ }
+
+ public LinkedHashSet<String> getViews() {
+ return views;
+ }
+
+ public void setViews(LinkedHashSet<String> aViews) {
+ views = aViews;
+ }
+
+ public LinkedHashSet<String> getFlags() {
+ return flags;
+ }
+
+ public void setFlags(LinkedHashSet<String> aFlags) {
+ flags = aFlags;
+ }
+
+ @JsonAnySetter
+ public void setFeature(String name, Object value) {
+ if (name.startsWith(JsonCas2Names.REF_FEATURE_PREFIX)) {
+ name = name.substring(1);
+ }
+
+ features.put(name, value);
+ }
+
+ @JsonAnyGetter
+ public Map<String, Object> getFeatures() {
+ return features;
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/flexjson/model/Json2Type.java b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/model/Json2Type.java
new file mode 100644
index 0000000..e418ac5
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/flexjson/model/Json2Type.java
@@ -0,0 +1,89 @@
+/*
+ * 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.flexjson.model;
+
+import static org.apache.uima.cas.CAS.TYPE_NAME_ANNOTATION;
+import static org.apache.uima.json.flexjson.FlexJsonNames.COMPONENT_TYPE_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.NAME_FIELD;
+import static org.apache.uima.json.flexjson.FlexJsonNames.SUPER_TYPE_FIELD;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+@Deprecated
+@JsonPropertyOrder(value = { "name", "parent", "componentType" })
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Json2Type {
+ @JsonProperty(value = NAME_FIELD, required = false)
+ private String name;
+
+ @JsonProperty(value = SUPER_TYPE_FIELD, required = true, defaultValue = TYPE_NAME_ANNOTATION)
+ private String parent;
+
+ @JsonProperty(value = COMPONENT_TYPE_FIELD, required = false)
+ private String componentType;
+
+ private Map<String, String> features = new LinkedHashMap<>();
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String aName) {
+ name = aName;
+ }
+
+ public String getParent() {
+ return parent;
+ }
+
+ public void setParent(String aParent) {
+ parent = aParent;
+ }
+
+ public String getComponentType() {
+ return componentType;
+ }
+
+ public void setComponentType(String aComponentType) {
+ componentType = aComponentType;
+ }
+
+ @JsonAnySetter
+ public void setProperty(String name, Object value) {
+ if (value instanceof String) {
+ features.put(name, (String) value);
+ return;
+ }
+
+ throw new IllegalArgumentException("Feature type names must be strings");
+ }
+
+ @SuppressWarnings("unchecked")
+ @JsonAnyGetter
+ public Map<String, Object> getProperties() {
+ return (Map) features;
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/JsonCas2Deserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/JsonCas2Deserializer.java
new file mode 100644
index 0000000..ee95e37
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/JsonCas2Deserializer.java
@@ -0,0 +1,99 @@
+/*
+ * 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.jsoncas2;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.json.jsoncas2.mode.FeatureStructuresMode;
+import org.apache.uima.json.jsoncas2.model.FeatureStructures;
+import org.apache.uima.json.jsoncas2.model.Views;
+import org.apache.uima.json.jsoncas2.ser.CasDeserializer;
+import org.apache.uima.json.jsoncas2.ser.FeatureDeserializer;
+import org.apache.uima.json.jsoncas2.ser.FeatureStructureDeserializer;
+import org.apache.uima.json.jsoncas2.ser.FeatureStructuresAsArrayDeserializer;
+import org.apache.uima.json.jsoncas2.ser.FeatureStructuresAsObjectDeserializer;
+import org.apache.uima.json.jsoncas2.ser.TypeDeserializer;
+import org.apache.uima.json.jsoncas2.ser.TypeSystemDeserializer;
+import org.apache.uima.json.jsoncas2.ser.ViewsDeserializer;
+import org.apache.uima.resource.metadata.FeatureDescription;
+import org.apache.uima.resource.metadata.TypeDescription;
+import org.apache.uima.resource.metadata.TypeSystemDescription;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+public class JsonCas2Deserializer {
+ private FeatureStructuresMode fsMode = FeatureStructuresMode.AS_ARRAY;
+ private ObjectMapper cachedMapper;
+
+ public void setFsMode(FeatureStructuresMode aFsMode) {
+ fsMode = aFsMode;
+ }
+
+ public FeatureStructuresMode getFsMode() {
+ return fsMode;
+ }
+
+ private synchronized ObjectMapper getMapper() {
+ if (cachedMapper == null) {
+ SimpleModule module = new SimpleModule("UIMA CAS JSON",
+ new Version(1, 0, 0, null, null, null));
+
+ module.addDeserializer(CAS.class, new CasDeserializer());
+ module.addDeserializer(FeatureStructure.class, new FeatureStructureDeserializer());
+
+ switch (fsMode) {
+ case AS_ARRAY:
+ module.addDeserializer(FeatureStructures.class,
+ new FeatureStructuresAsArrayDeserializer());
+ break;
+ case AS_OBJECT:
+ module.addDeserializer(FeatureStructures.class,
+ new FeatureStructuresAsObjectDeserializer());
+ break;
+ }
+
+ module.addDeserializer(FeatureDescription.class, new FeatureDeserializer());
+ module.addDeserializer(TypeDescription.class, new TypeDeserializer());
+ module.addDeserializer(TypeSystemDescription.class, new TypeSystemDeserializer());
+ module.addDeserializer(Views.class, new ViewsDeserializer());
+
+ cachedMapper = new ObjectMapper();
+ cachedMapper.registerModule(module);
+ }
+ return cachedMapper;
+ }
+
+ public void deserialize(File aSourceFile, CAS aTargetCas) throws IOException {
+ getMapper().reader().forType(CAS.class) //
+ .withAttribute(CasDeserializer.CONTEXT_CAS, aTargetCas) //
+ .readValue(aSourceFile);
+ }
+
+ public void deserialize(InputStream aSourceStream, CAS aTargetCas) throws IOException {
+ getMapper().reader().forType(CAS.class) //
+ .withAttribute(CasDeserializer.CONTEXT_CAS, aTargetCas) //
+ .readValue(aSourceStream);
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/JsonCas2Names.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/JsonCas2Names.java
new file mode 100644
index 0000000..81c725b
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/JsonCas2Names.java
@@ -0,0 +1,101 @@
+/*
+ * 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.jsoncas2;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Type;
+import org.apache.uima.cas.impl.TypeImpl;
+import org.apache.uima.resource.metadata.FeatureDescription;
+import org.apache.uima.resource.metadata.TypeDescription;
+
+public class JsonCas2Names {
+ public static final String RESERVED_FIELD_PREFIX = "%";
+
+ public static final String TYPE_FIELD = RESERVED_FIELD_PREFIX + "TYPE";
+
+ public static final String RANGE_FIELD = RESERVED_FIELD_PREFIX + "RANGE";
+
+ public static final String HEADER_FIELD = RESERVED_FIELD_PREFIX + "HEADER";
+
+ public static final String TYPES_FIELD = RESERVED_FIELD_PREFIX + "TYPES";
+
+ public static final String FEATURES_FIELD = RESERVED_FIELD_PREFIX + "FEATURES";
+
+ public static final String VIEWS_FIELD = RESERVED_FIELD_PREFIX + "VIEWS";
+
+ /**
+ * @see CAS#getSofa()
+ */
+ public static final String VIEW_SOFA_FIELD = RESERVED_FIELD_PREFIX + "SOFA";
+
+ public static final String VIEW_MEMBERS_FIELD = RESERVED_FIELD_PREFIX + "MEMBERS";
+
+ public static final String FEATURE_STRUCTURES_FIELD = RESERVED_FIELD_PREFIX
+ + "FEATURE_STRUCTURES";
+
+ public static final String REF_FEATURE_PREFIX = "@";
+
+ public static final String NUMERIC_FEATURE_PREFIX = "#";
+
+ public static final String ANCHOR_FEATURE_PREFIX = "^";
+
+ public static final String NAME_FIELD = RESERVED_FIELD_PREFIX + "NAME";
+
+ /**
+ * @see TypeDescription#getSupertypeName()
+ * @see TypeImpl#getSuperType()
+ */
+ public static final String SUPER_TYPE_FIELD = RESERVED_FIELD_PREFIX + "SUPER_TYPE";
+
+ /**
+ * @see TypeDescription#getDescription()
+ */
+ public static final String DESCRIPTION_FIELD = RESERVED_FIELD_PREFIX + "DESCRIPTION";
+
+ /**
+ * @see FeatureDescription#getElementType()
+ * @see Type#getComponentType()
+ */
+ public static final String ELEMENT_TYPE_FIELD = RESERVED_FIELD_PREFIX + "ELEMENT_TYPE";
+
+ public static final String MULTIPLE_REFERENCES_ALLOWED_FIELD = RESERVED_FIELD_PREFIX
+ + "MULTIPLE_REFERENCES_ALLOWED";
+
+ public static final String ID_FIELD = RESERVED_FIELD_PREFIX + "ID";
+
+ // public static final String FLAGS_FIELD = RESERVED_FIELD_PREFIX + "FLAGS";
+
+ public static final String FLAG_DOCUMENT_ANNOTATION = "DocumentAnnotation";
+
+ public static final String ARRAY_SUFFIX = "[]";
+
+ public static final String ELEMENTS_FIELD = RESERVED_FIELD_PREFIX + "ELEMENTS";
+
+ public static final String HEADER_OFFSET_ENCODING = "offset-encoding";
+
+ public static final String NUMBER_FLOAT_NAN = "NaN";
+
+ public static final String NUMBER_FLOAT_POSITIVE_INFINITY = "Infinity";
+
+ public static final String NUMBER_FLOAT_POSITIVE_INFINITY_ABBR = "Inf";
+
+ public static final String NUMBER_FLOAT_NEGATIVE_INFINITY = "-Infinity";
+
+ public static final String NUMBER_FLOAT_NEGATIVE_INFINITY_ABBR = "-Inf";
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/JsonCas2Serializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/JsonCas2Serializer.java
new file mode 100644
index 0000000..978b809
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/JsonCas2Serializer.java
@@ -0,0 +1,177 @@
+/*
+ * 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.jsoncas2;
+
+import static org.apache.uima.json.jsoncas2.mode.FeatureStructuresMode.AS_ARRAY;
+import static org.apache.uima.json.jsoncas2.mode.OffsetConversionMode.UTF_16;
+import static org.apache.uima.json.jsoncas2.mode.SofaMode.AS_REGULAR_FEATURE_STRUCTURE;
+import static org.apache.uima.json.jsoncas2.mode.TypeSystemMode.FULL;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.function.ToIntFunction;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.Type;
+import org.apache.uima.json.jsoncas2.mode.FeatureStructuresMode;
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+import org.apache.uima.json.jsoncas2.mode.SofaMode;
+import org.apache.uima.json.jsoncas2.mode.TypeSystemMode;
+import org.apache.uima.json.jsoncas2.ref.FullyQualifiedTypeRefGenerator;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+import org.apache.uima.json.jsoncas2.ref.SequentialIdRefGenerator;
+import org.apache.uima.json.jsoncas2.ser.CasSerializer;
+import org.apache.uima.json.jsoncas2.ser.CommonArrayFSSerializer;
+import org.apache.uima.json.jsoncas2.ser.FeatureSerializer;
+import org.apache.uima.json.jsoncas2.ser.FeatureStructureSerializer;
+import org.apache.uima.json.jsoncas2.ser.FeatureStructuresAsArraySerializer;
+import org.apache.uima.json.jsoncas2.ser.FeatureStructuresAsObjectSerializer;
+import org.apache.uima.json.jsoncas2.ser.SofaSerializer;
+import org.apache.uima.json.jsoncas2.ser.TypeSerializer;
+import org.apache.uima.json.jsoncas2.ser.TypeSystemSerializer;
+import org.apache.uima.json.jsoncas2.ser.ViewsSerializer;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+public class JsonCas2Serializer {
+
+ private FeatureStructuresMode fsMode = AS_ARRAY;
+ private SofaMode sofaMode = AS_REGULAR_FEATURE_STRUCTURE;
+ private TypeSystemMode typeSystemMode = FULL;
+ private OffsetConversionMode offsetConversionMode = UTF_16;
+ private ObjectMapper cachedMapper;
+ private Supplier<ToIntFunction<FeatureStructure>> idRefGeneratorSupplier = SequentialIdRefGenerator::new;
+ private Supplier<Function<Type, String>> typeRefGeneratorSupplier = FullyQualifiedTypeRefGenerator::new;
+
+ public void setFsMode(FeatureStructuresMode aFsMode) {
+ fsMode = aFsMode;
+ }
+
+ public FeatureStructuresMode getFsMode() {
+ return fsMode;
+ }
+
+ public void setSofaMode(SofaMode aSofaMode) {
+ sofaMode = aSofaMode;
+ }
+
+ public SofaMode getSofaMode() {
+ return sofaMode;
+ }
+
+ public void setOffsetConversionMode(OffsetConversionMode aOffsetConversionMode) {
+ offsetConversionMode = aOffsetConversionMode;
+ }
+
+ public OffsetConversionMode getOffsetConversionMode() {
+ return offsetConversionMode;
+ }
+
+ public void setIdRefGeneratorSupplier(
+ Supplier<ToIntFunction<FeatureStructure>> aIdRefGeneratorSupplier) {
+ idRefGeneratorSupplier = aIdRefGeneratorSupplier;
+ }
+
+ public Supplier<ToIntFunction<FeatureStructure>> getIdRefGeneratorSupplier() {
+ return idRefGeneratorSupplier;
+ }
+
+ public void setTypeRefGeneratorSupplier(
+ Supplier<Function<Type, String>> aTypeRefGeneratorSupplier) {
+ typeRefGeneratorSupplier = aTypeRefGeneratorSupplier;
+ }
+
+ public Supplier<Function<Type, String>> getTypeRefGeneratorSupplier() {
+ return typeRefGeneratorSupplier;
+ }
+
+ public void setTypeSystemMode(TypeSystemMode aMode) {
+ typeSystemMode = aMode;
+ }
+
+ public TypeSystemMode getTypeSystemMode() {
+ return typeSystemMode;
+ }
+
+ private synchronized ObjectMapper getMapper() {
+ if (cachedMapper == null) {
+ SimpleModule module = new SimpleModule("UIMA CAS JSON",
+ new Version(1, 0, 0, null, null, null));
+
+ ReferenceCache.Builder refCacheBuilder = ReferenceCache.builder()
+ .withIdRefGeneratorSupplier(idRefGeneratorSupplier)
+ .withTypeRefGeneratorSupplier(typeRefGeneratorSupplier);
+ module.addSerializer(new CasSerializer(refCacheBuilder::build));
+ module.addSerializer(new TypeSystemSerializer());
+ module.addSerializer(new TypeSerializer());
+ module.addSerializer(new FeatureSerializer());
+ module.addSerializer(new CommonArrayFSSerializer());
+
+ switch (sofaMode) {
+ case AS_PART_OF_VIEW:
+ module.addSerializer(new SofaSerializer());
+ break;
+ case AS_REGULAR_FEATURE_STRUCTURE:
+ // Nothing to do
+ break;
+ }
+
+ module.addSerializer(new FeatureStructureSerializer());
+
+ switch (fsMode) {
+ case AS_ARRAY:
+ module.addSerializer(new FeatureStructuresAsArraySerializer());
+ break;
+ case AS_OBJECT:
+ module.addSerializer(new FeatureStructuresAsObjectSerializer());
+ break;
+ }
+
+ module.addSerializer(new ViewsSerializer());
+
+ cachedMapper = new ObjectMapper();
+ cachedMapper.registerModule(module);
+ }
+
+ return cachedMapper;
+ }
+
+ private ObjectWriter getWriter() {
+ return getMapper().writerWithDefaultPrettyPrinter() //
+ .withAttribute(SofaMode.KEY, sofaMode) //
+ .withAttribute(FeatureStructuresMode.KEY, fsMode)
+ .withAttribute(OffsetConversionMode.KEY, offsetConversionMode)
+ .withAttribute(TypeSystemMode.KEY, typeSystemMode);
+ }
+
+ public void serialize(CAS aCas, File aTargetFile) throws IOException {
+ getWriter().writeValue(aTargetFile, aCas);
+ }
+
+ public void serialize(CAS aCas, OutputStream aTargetStream) throws IOException {
+ getWriter().writeValue(aTargetStream, aCas);
+ }
+}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/OffsetConverter.java
similarity index 60%
copy from uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
copy to uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/OffsetConverter.java
index 6ba69f5..a273cf3 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/OffsetConverter.java
@@ -16,18 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.uima.cas.serdes;
+package org.apache.uima.json.jsoncas2.encoding;
-import static org.junit.jupiter.api.Assumptions.assumeFalse;
+public interface OffsetConverter {
+ int mapExternal(int aOffset);
-import java.util.regex.Pattern;
-
-public class SerDesAssuptions {
- public static void assumeNotKnownToFail(Runnable aScenario, String... aPatternsAndReasons) {
- for (int i = 0; i < aPatternsAndReasons.length; i += 2) {
- String pattern = aPatternsAndReasons[i];
- String reason = aPatternsAndReasons[i + 1];
- assumeFalse(Pattern.matches(pattern, aScenario.toString()), "Skipped because: " + reason);
- }
- }
+ int mapInternal(int aOffset);
}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/Utf16CodeunitOffsetConverter.java
similarity index 60%
copy from uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
copy to uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/Utf16CodeunitOffsetConverter.java
index 6ba69f5..f09f9ca 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/Utf16CodeunitOffsetConverter.java
@@ -16,18 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.uima.cas.serdes;
+package org.apache.uima.json.jsoncas2.encoding;
-import static org.junit.jupiter.api.Assumptions.assumeFalse;
+public class Utf16CodeunitOffsetConverter implements OffsetConverter {
+ public Utf16CodeunitOffsetConverter(String aString) {
+ // NOP
+ }
-import java.util.regex.Pattern;
+ @Override
+ public int mapExternal(int aOffset) {
+ return aOffset;
+ }
-public class SerDesAssuptions {
- public static void assumeNotKnownToFail(Runnable aScenario, String... aPatternsAndReasons) {
- for (int i = 0; i < aPatternsAndReasons.length; i += 2) {
- String pattern = aPatternsAndReasons[i];
- String reason = aPatternsAndReasons[i + 1];
- assumeFalse(Pattern.matches(pattern, aScenario.toString()), "Skipped because: " + reason);
- }
+ @Override
+ public int mapInternal(int aOffset) {
+ return aOffset;
}
}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/Utf32CodepointOffsetConverter.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/Utf32CodepointOffsetConverter.java
new file mode 100644
index 0000000..e4fa981
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/Utf32CodepointOffsetConverter.java
@@ -0,0 +1,84 @@
+/*
+ * 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.jsoncas2.encoding;
+
+import static java.util.Arrays.fill;
+
+public class Utf32CodepointOffsetConverter implements OffsetConverter {
+ public static final int UNMAPPED = Integer.MIN_VALUE;
+
+ private final int[] internalToExternal;
+ private final int[] externalToInternal;
+
+ public Utf32CodepointOffsetConverter(String aString) {
+ if (aString == null) {
+ internalToExternal = null;
+ externalToInternal = null;
+ return;
+ }
+
+ int codePointCount = aString.codePointCount(0, aString.length());
+ externalToInternal = new int[codePointCount + 1];
+ fill(externalToInternal, UNMAPPED);
+
+ int codeUnitCount = aString.length();
+ internalToExternal = new int[codeUnitCount + 1];
+ fill(internalToExternal, UNMAPPED);
+
+ int cpi = 0;
+ int cui = 0;
+ while (cui < aString.length()) {
+ int cp = aString.codePointAt(cui);
+ externalToInternal[cpi] = cui;
+ internalToExternal[cui] = cpi;
+
+ cpi++;
+ cui += Character.charCount(cp);
+ }
+
+ externalToInternal[cpi] = cui;
+ internalToExternal[cui] = cpi;
+ }
+
+ @Override
+ public int mapExternal(int aOffset) {
+ if (externalToInternal == null) {
+ return aOffset;
+ }
+
+ if (aOffset >= externalToInternal.length) {
+ return UNMAPPED;
+ }
+
+ return externalToInternal[aOffset];
+ }
+
+ @Override
+ public int mapInternal(int aOffset) {
+ if (internalToExternal == null) {
+ return aOffset;
+ }
+
+ if (aOffset >= internalToExternal.length) {
+ return UNMAPPED;
+ }
+
+ return internalToExternal[aOffset];
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/Utf8ByteOffsetConverter.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/Utf8ByteOffsetConverter.java
new file mode 100644
index 0000000..695602c
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/encoding/Utf8ByteOffsetConverter.java
@@ -0,0 +1,87 @@
+/*
+ * 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.jsoncas2.encoding;
+
+import static java.lang.Character.charCount;
+import static java.lang.Character.toChars;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Arrays.fill;
+
+public class Utf8ByteOffsetConverter implements OffsetConverter {
+ public static final int UNMAPPED = Integer.MIN_VALUE;
+
+ private final int[] internalToExternal;
+ private final int[] externalToInternal;
+
+ public Utf8ByteOffsetConverter(String aString) {
+ if (aString == null) {
+ internalToExternal = null;
+ externalToInternal = null;
+ return;
+ }
+
+ int byteCount = aString.getBytes(UTF_8).length;
+ externalToInternal = new int[byteCount + 1];
+ fill(externalToInternal, UNMAPPED);
+
+ int codeUnitCount = aString.length();
+ internalToExternal = new int[codeUnitCount + 1];
+ fill(internalToExternal, UNMAPPED);
+
+ int cbi = 0;
+ int cui = 0;
+ while (cui < aString.length()) {
+ externalToInternal[cbi] = cui;
+ internalToExternal[cui] = cbi;
+
+ int cp = aString.codePointAt(cui);
+ cbi += String.valueOf(toChars(cp)).getBytes(UTF_8).length;
+ cui += charCount(cp);
+ }
+
+ externalToInternal[cbi] = cui;
+ internalToExternal[cui] = cbi;
+ }
+
+ @Override
+ public int mapExternal(int aOffset) {
+ if (externalToInternal == null) {
+ return aOffset;
+ }
+
+ if (aOffset >= externalToInternal.length) {
+ return UNMAPPED;
+ }
+
+ return externalToInternal[aOffset];
+ }
+
+ @Override
+ public int mapInternal(int aOffset) {
+ if (internalToExternal == null) {
+ return aOffset;
+ }
+
+ if (aOffset >= internalToExternal.length) {
+ return UNMAPPED;
+ }
+
+ return internalToExternal[aOffset];
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/ArrayTypeMode.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/ArrayTypeMode.java
new file mode 100644
index 0000000..7683f6c
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/ArrayTypeMode.java
@@ -0,0 +1,45 @@
+/*
+ * 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.jsoncas2.mode;
+
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+public enum ArrayTypeMode {
+ /**
+ * Serialize array fields using the the compact {@code TypeName[]} syntax.
+ */
+ AS_ARRAY_TYPED_RANGE,
+
+ /**
+ * Serialize array fields using the traditional {@code range} and {@code elementType} separation
+ * that we know from the XML type system descriptors.
+ */
+ AS_RANGE_AND_ELEMENT;
+
+ public static final String KEY = "UIMA.FeatureStructuresMode";
+
+ public static void set(SerializerProvider aProvider, ArrayTypeMode aMode) {
+ aProvider.setAttribute(KEY, aMode);
+ }
+
+ public static ArrayTypeMode get(SerializerProvider aProvider) {
+ ArrayTypeMode mode = (ArrayTypeMode) aProvider.getAttribute(KEY);
+ return mode != null ? mode : AS_RANGE_AND_ELEMENT;
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/FeatureStructuresMode.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/FeatureStructuresMode.java
new file mode 100644
index 0000000..65c7b35
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/FeatureStructuresMode.java
@@ -0,0 +1,36 @@
+/*
+ * 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.jsoncas2.mode;
+
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+public enum FeatureStructuresMode {
+ AS_OBJECT, AS_ARRAY;
+
+ public static final String KEY = "UIMA.FeatureStructuresMode";
+
+ public static void set(SerializerProvider aProvider, FeatureStructuresMode aMode) {
+ aProvider.setAttribute(KEY, aMode);
+ }
+
+ public static FeatureStructuresMode get(SerializerProvider aProvider) {
+ FeatureStructuresMode mode = (FeatureStructuresMode) aProvider.getAttribute(KEY);
+ return mode != null ? mode : AS_ARRAY;
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/OffsetConversionMode.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/OffsetConversionMode.java
new file mode 100644
index 0000000..da20ec2
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/OffsetConversionMode.java
@@ -0,0 +1,87 @@
+/*
+ * 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.jsoncas2.mode;
+
+import java.util.Optional;
+
+import org.apache.uima.json.jsoncas2.encoding.OffsetConverter;
+import org.apache.uima.json.jsoncas2.encoding.Utf16CodeunitOffsetConverter;
+import org.apache.uima.json.jsoncas2.encoding.Utf32CodepointOffsetConverter;
+import org.apache.uima.json.jsoncas2.encoding.Utf8ByteOffsetConverter;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.DatabindContext;
+
+public enum OffsetConversionMode {
+ @JsonProperty("UTF-8") //
+ UTF_8, //
+ @JsonProperty("UTF-16") //
+ UTF_16, //
+ @JsonProperty("UTF-32") //
+ UTF_32;
+
+ private static final String SEPARATOR = "::";
+ public static final String KEY = "UIMA.OffsetConversionMode";
+
+ public static void set(DatabindContext aProvider, OffsetConversionMode aMode) {
+ aProvider.setAttribute(KEY, aMode);
+ }
+
+ public static OffsetConversionMode getDefault() {
+ return UTF_16;
+ }
+
+ public static OffsetConversionMode get(DatabindContext aProvider) {
+ return (OffsetConversionMode) aProvider.getAttribute(KEY);
+ }
+
+ public static OffsetConversionMode getOrDefault(DatabindContext aProvider) {
+ OffsetConversionMode mode = get(aProvider);
+ if (mode != null) {
+ return mode;
+ }
+ return getDefault();
+ }
+
+ public static OffsetConverter initConverter(DatabindContext aProvider, String aView,
+ String aText) {
+ OffsetConverter converter;
+ switch (getOrDefault(aProvider)) {
+ case UTF_8:
+ converter = new Utf8ByteOffsetConverter(aText);
+ break;
+ case UTF_16:
+ converter = new Utf16CodeunitOffsetConverter(aText);
+ break;
+ case UTF_32:
+ converter = new Utf32CodepointOffsetConverter(aText);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported conversion mode: [" + aProvider + "]");
+ }
+
+ aProvider.setAttribute(KEY + SEPARATOR + aView, converter);
+
+ return converter;
+ }
+
+ public static Optional<OffsetConverter> getConverter(DatabindContext aProvider, String aSofaId) {
+ return Optional.ofNullable((OffsetConverter) aProvider.getAttribute(KEY + SEPARATOR + aSofaId));
+ }
+}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/SofaMode.java
similarity index 60%
copy from uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
copy to uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/SofaMode.java
index 6ba69f5..3e31339 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/SofaMode.java
@@ -16,18 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.uima.cas.serdes;
+package org.apache.uima.json.jsoncas2.mode;
-import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import com.fasterxml.jackson.databind.SerializerProvider;
-import java.util.regex.Pattern;
+public enum SofaMode {
+ AS_REGULAR_FEATURE_STRUCTURE, AS_PART_OF_VIEW;
-public class SerDesAssuptions {
- public static void assumeNotKnownToFail(Runnable aScenario, String... aPatternsAndReasons) {
- for (int i = 0; i < aPatternsAndReasons.length; i += 2) {
- String pattern = aPatternsAndReasons[i];
- String reason = aPatternsAndReasons[i + 1];
- assumeFalse(Pattern.matches(pattern, aScenario.toString()), "Skipped because: " + reason);
- }
+ public static final String KEY = "UIMA.SofaMode";
+
+ public static void set(SerializerProvider aProvider, SofaMode aMode) {
+ aProvider.setAttribute(KEY, aMode);
+ }
+
+ public static SofaMode get(SerializerProvider aProvider) {
+ return (SofaMode) aProvider.getAttribute(KEY);
}
}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/TypeSystemMode.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/TypeSystemMode.java
new file mode 100644
index 0000000..68db852
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/TypeSystemMode.java
@@ -0,0 +1,51 @@
+/*
+ * 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.jsoncas2.mode;
+
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+public enum TypeSystemMode {
+
+ /**
+ * Include the full type system in the JSON file.
+ */
+ FULL,
+
+ /**
+ * Include only the types actually used.
+ */
+ MINIMAL,
+
+ /**
+ * Do not include the type system in the JSON file. The reader must obtain the type system by some
+ * other means.
+ */
+ NONE;
+
+ public static final String KEY = "UIMA.TypeSystemMode";
+
+ public static void set(SerializerProvider aProvider, TypeSystemMode aMode) {
+ aProvider.setAttribute(KEY, aMode);
+ }
+
+ public static TypeSystemMode get(SerializerProvider aProvider) {
+ TypeSystemMode mode = (TypeSystemMode) aProvider.getAttribute(KEY);
+ return mode != null ? mode : FULL;
+ }
+}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/ViewsMode.java
similarity index 60%
copy from uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
copy to uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/ViewsMode.java
index 6ba69f5..8c155f2 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/mode/ViewsMode.java
@@ -16,18 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.uima.cas.serdes;
+package org.apache.uima.json.jsoncas2.mode;
-import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import com.fasterxml.jackson.databind.SerializerProvider;
-import java.util.regex.Pattern;
+public enum ViewsMode {
+ SEPARATE, INLINE;
-public class SerDesAssuptions {
- public static void assumeNotKnownToFail(Runnable aScenario, String... aPatternsAndReasons) {
- for (int i = 0; i < aPatternsAndReasons.length; i += 2) {
- String pattern = aPatternsAndReasons[i];
- String reason = aPatternsAndReasons[i + 1];
- assumeFalse(Pattern.matches(pattern, aScenario.toString()), "Skipped because: " + reason);
- }
+ public static final String KEY = "UIMA.ViewsMode";
+
+ public static void set(SerializerProvider aProvider, ViewsMode aMode) {
+ aProvider.setAttribute(KEY, aMode);
+ }
+
+ public static ViewsMode get(SerializerProvider aProvider) {
+ ViewsMode mode = (ViewsMode) aProvider.getAttribute(KEY);
+ return mode != null ? mode : SEPARATE;
}
}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/model/FeatureStructures.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/model/FeatureStructures.java
new file mode 100644
index 0000000..109050d
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/model/FeatureStructures.java
@@ -0,0 +1,49 @@
+/*
+ * 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.jsoncas2.model;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.uima.cas.FeatureStructure;
+
+public class FeatureStructures implements Iterable<FeatureStructure> {
+ private final List<FeatureStructure> featureStructures;
+
+ public FeatureStructures(Collection<FeatureStructure> aFeatureStructures) {
+ featureStructures = aFeatureStructures.stream() //
+ .sorted(Comparator.comparing(fs -> {
+ return fs.getType().getName();
+ })) //
+ .collect(toList());
+ }
+
+ @Override
+ public Iterator<FeatureStructure> iterator() {
+ return featureStructures.iterator();
+ }
+
+ public boolean isEmpty() {
+ return featureStructures.isEmpty();
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/model/Header.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/model/Header.java
new file mode 100644
index 0000000..21a55a7
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/model/Header.java
@@ -0,0 +1,51 @@
+/*
+ * 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.jsoncas2.model;
+
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.HEADER_OFFSET_ENCODING;
+
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.DatabindContext;
+
+public class Header {
+ @JsonProperty(HEADER_OFFSET_ENCODING)
+ private OffsetConversionMode offsetEncoding;
+
+ public Header() {
+ // Used for deserialization
+ }
+
+ public Header(DatabindContext aProvider) {
+ offsetEncoding = OffsetConversionMode.get(aProvider);
+ }
+
+ public OffsetConversionMode getOffsetEncoding() {
+ return offsetEncoding;
+ }
+
+ public void setOffsetEncoding(OffsetConversionMode aOffsetEncoding) {
+ offsetEncoding = aOffsetEncoding;
+ }
+
+ public boolean requiresSerialization() {
+ return offsetEncoding != null && offsetEncoding != OffsetConversionMode.getDefault();
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/model/Views.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/model/Views.java
new file mode 100644
index 0000000..ffab6a3
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/model/Views.java
@@ -0,0 +1,47 @@
+/*
+ * 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.jsoncas2.model;
+
+import static java.util.Collections.sort;
+import static java.util.Comparator.comparing;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.uima.cas.CAS;
+
+public class Views implements Iterable<CAS> {
+ private final List<CAS> views;
+
+ public Views(CAS aCas) {
+ views = new ArrayList<>();
+ aCas.getViewIterator().forEachRemaining(views::add);
+ sort(views, comparing(CAS::getViewName));
+ }
+
+ @Override
+ public Iterator<CAS> iterator() {
+ return views.iterator();
+ }
+
+ public boolean isEmpty() {
+ return views.isEmpty();
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FeatureStructureIdToViewIndex.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FeatureStructureIdToViewIndex.java
new file mode 100644
index 0000000..537f9c8
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FeatureStructureIdToViewIndex.java
@@ -0,0 +1,54 @@
+/*
+ * 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.jsoncas2.ref;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.databind.DatabindContext;
+
+public class FeatureStructureIdToViewIndex {
+ public static final String FS_VIEW_CACHE = "UIMA.FeatureStructureIdToViewIndex";
+
+ // FIXME Should use a map implementation with a primitive key!
+ private Map<Integer, Set<String>> fsIdToViewsCache;
+
+ public FeatureStructureIdToViewIndex() {
+ fsIdToViewsCache = new HashMap<>();
+ }
+
+ public Set<String> getViewsContainingFs(int aFsId) {
+ return fsIdToViewsCache.getOrDefault(aFsId, Collections.emptySet());
+ }
+
+ public void assignFsToView(int aFsId, String aView) {
+ fsIdToViewsCache.computeIfAbsent(aFsId, _fsId -> new HashSet<>()).add(aView);
+ }
+
+ public static void set(DatabindContext aProvider, FeatureStructureIdToViewIndex aRefCache) {
+ aProvider.setAttribute(FS_VIEW_CACHE, aRefCache);
+ }
+
+ public static FeatureStructureIdToViewIndex get(DatabindContext aProvider) {
+ return (FeatureStructureIdToViewIndex) aProvider.getAttribute(FS_VIEW_CACHE);
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FeatureStructureToIdIndex.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FeatureStructureToIdIndex.java
new file mode 100644
index 0000000..50e31bf
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FeatureStructureToIdIndex.java
@@ -0,0 +1,72 @@
+/*
+ * 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.jsoncas2.ref;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+
+import org.apache.uima.cas.FeatureStructure;
+
+import com.fasterxml.jackson.databind.DatabindContext;
+
+public class FeatureStructureToIdIndex {
+ public static final String KEY = "UIMA.FeatureStructureToIdIndex";
+
+ private Map<FeatureStructure, Integer> fsToIdIndex;
+ private Map<Integer, FeatureStructure> idToFsIndex;
+
+ public FeatureStructureToIdIndex() {
+ idToFsIndex = new HashMap<>();
+ fsToIdIndex = new HashMap<>();
+ }
+
+ public void put(int aFsId, FeatureStructure aFs) {
+ idToFsIndex.put(aFsId, aFs);
+ fsToIdIndex.put(aFs, aFsId);
+ }
+
+ public OptionalInt get(FeatureStructure aFs) {
+ Integer id = fsToIdIndex.get(aFs);
+ return id != null ? OptionalInt.of(id) : OptionalInt.empty();
+ }
+
+ public List<Entry<Integer, FeatureStructure>> getAllFeatureStructures() {
+ return idToFsIndex.entrySet().stream() //
+ .sorted(Comparator.comparing(Entry::getKey)) //
+ .collect(Collectors.toList());
+ }
+
+ public Optional<FeatureStructure> get(int aId) {
+ return Optional.ofNullable(idToFsIndex.get(aId));
+ }
+
+ public static void set(DatabindContext aProvider, FeatureStructureToIdIndex aRefCache) {
+ aProvider.setAttribute(KEY, aRefCache);
+ }
+
+ public static FeatureStructureToIdIndex get(DatabindContext aProvider) {
+ return (FeatureStructureToIdIndex) aProvider.getAttribute(KEY);
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FeatureStructureToViewIndex.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FeatureStructureToViewIndex.java
new file mode 100644
index 0000000..f2ced13
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FeatureStructureToViewIndex.java
@@ -0,0 +1,71 @@
+/*
+ * 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.jsoncas2.ref;
+
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.json.jsoncas2.model.FeatureStructures;
+
+import com.fasterxml.jackson.databind.DatabindContext;
+
+public class FeatureStructureToViewIndex {
+ public static final String FS_VIEW_CACHE = "UIMA.FeatureStructureToViewIndex";
+
+ private final FeatureStructures featureStructures;
+
+ private Map<FeatureStructure, Set<String>> fsToViewsCache;
+
+ public FeatureStructureToViewIndex() {
+ featureStructures = null;
+ fsToViewsCache = new IdentityHashMap<>();
+ }
+
+ public FeatureStructureToViewIndex(FeatureStructures aFeatureStructures) {
+ featureStructures = aFeatureStructures;
+ }
+
+ public Set<String> getViewsContainingFs(FeatureStructure aFS) {
+ if (fsToViewsCache == null) {
+ fsToViewsCache = new IdentityHashMap<>();
+ featureStructures.iterator().next().getCAS().getViewIterator().forEachRemaining(view -> {
+ for (FeatureStructure fs : view.select()) {
+ fsToViewsCache.computeIfAbsent(fs, _fs -> new HashSet<>()).add(view.getViewName());
+ }
+ });
+ }
+
+ return fsToViewsCache.get(aFS);
+ }
+
+ public void assignFsToView(FeatureStructure aFs, String aView) {
+ fsToViewsCache.computeIfAbsent(aFs, _fs -> new HashSet<>()).add(aView);
+ }
+
+ public static void set(DatabindContext aProvider, FeatureStructureToViewIndex aRefCache) {
+ aProvider.setAttribute(FS_VIEW_CACHE, aRefCache);
+ }
+
+ public static FeatureStructureToViewIndex get(DatabindContext aProvider) {
+ return (FeatureStructureToViewIndex) aProvider.getAttribute(FS_VIEW_CACHE);
+ }
+}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FullyQualifiedTypeRefGenerator.java
similarity index 60%
copy from uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
copy to uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FullyQualifiedTypeRefGenerator.java
index 6ba69f5..e73d0a1 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/FullyQualifiedTypeRefGenerator.java
@@ -16,18 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.uima.cas.serdes;
+package org.apache.uima.json.jsoncas2.ref;
-import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import java.util.function.Function;
-import java.util.regex.Pattern;
+import org.apache.uima.cas.Type;
-public class SerDesAssuptions {
- public static void assumeNotKnownToFail(Runnable aScenario, String... aPatternsAndReasons) {
- for (int i = 0; i < aPatternsAndReasons.length; i += 2) {
- String pattern = aPatternsAndReasons[i];
- String reason = aPatternsAndReasons[i + 1];
- assumeFalse(Pattern.matches(pattern, aScenario.toString()), "Skipped because: " + reason);
- }
+public class FullyQualifiedTypeRefGenerator implements Function<Type, String> {
+ @Override
+ public String apply(Type aType) {
+ return aType.getName();
}
}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/ReferenceCache.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/ReferenceCache.java
new file mode 100644
index 0000000..58ac5b2
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/ReferenceCache.java
@@ -0,0 +1,99 @@
+/*
+ * 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.jsoncas2.ref;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.function.ToIntFunction;
+
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.Type;
+
+import com.fasterxml.jackson.databind.DatabindContext;
+
+public class ReferenceCache {
+ public static final String KEY = "UIMA.ReferenceCache";
+
+ private final ToIntFunction<FeatureStructure> idRefGenerator;
+ private Map<FeatureStructure, Integer> idRefCache = new HashMap<>();
+
+ private final Function<Type, String> typeRefGenerator;
+ private Map<Type, String> typeRefCache = new HashMap<>();
+
+ private ReferenceCache(Builder builder) {
+ idRefGenerator = builder.idRefGeneratorSupplier.get();
+ typeRefGenerator = builder.typeRefGeneratorSupplier.get();
+ }
+
+ public int fsRef(FeatureStructure aFs) {
+ return idRefCache.computeIfAbsent(aFs, _fs -> idRefGenerator.applyAsInt(_fs));
+ }
+
+ public String typeRef(Type aType) {
+ return typeRefCache.computeIfAbsent(aType, typeRefGenerator);
+ }
+
+ /**
+ * Creates builder to build {@link ReferenceCache}.
+ *
+ * @return created builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder to build {@link ReferenceCache}.
+ */
+ public static final class Builder {
+ private Supplier<ToIntFunction<FeatureStructure>> idRefGeneratorSupplier;
+ private Supplier<Function<Type, String>> typeRefGeneratorSupplier;
+
+ private Builder() {
+ idRefGeneratorSupplier = SequentialIdRefGenerator::new;
+ typeRefGeneratorSupplier = FullyQualifiedTypeRefGenerator::new;
+ }
+
+ public Builder withIdRefGeneratorSupplier(
+ Supplier<ToIntFunction<FeatureStructure>> idRefGeneratorSupplier) {
+ this.idRefGeneratorSupplier = idRefGeneratorSupplier;
+ return this;
+ }
+
+ public Builder withTypeRefGeneratorSupplier(
+ Supplier<Function<Type, String>> typeRefGeneratorSupplier) {
+ this.typeRefGeneratorSupplier = typeRefGeneratorSupplier;
+ return this;
+ }
+
+ public ReferenceCache build() {
+ return new ReferenceCache(this);
+ }
+ }
+
+ public static void set(DatabindContext aProvider, ReferenceCache aRefCache) {
+ aProvider.setAttribute(KEY, aRefCache);
+ }
+
+ public static ReferenceCache get(DatabindContext aProvider) {
+ return (ReferenceCache) aProvider.getAttribute(KEY);
+ }
+}
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/SequentialIdRefGenerator.java
similarity index 60%
copy from uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
copy to uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/SequentialIdRefGenerator.java
index 6ba69f5..8d9f8b8 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/serdes/SerDesAssuptions.java
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/SequentialIdRefGenerator.java
@@ -16,18 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.uima.cas.serdes;
+package org.apache.uima.json.jsoncas2.ref;
-import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import java.util.function.ToIntFunction;
-import java.util.regex.Pattern;
+import org.apache.uima.cas.FeatureStructure;
-public class SerDesAssuptions {
- public static void assumeNotKnownToFail(Runnable aScenario, String... aPatternsAndReasons) {
- for (int i = 0; i < aPatternsAndReasons.length; i += 2) {
- String pattern = aPatternsAndReasons[i];
- String reason = aPatternsAndReasons[i + 1];
- assumeFalse(Pattern.matches(pattern, aScenario.toString()), "Skipped because: " + reason);
- }
+public class SequentialIdRefGenerator implements ToIntFunction<FeatureStructure> {
+ /**
+ * IDs start at 1. 0 is reserved.
+ *
+ * @see "org.apache.uima.cas.impl.XmiCasSerializer.XmiDocSerializer#writeNullObject()"
+ */
+ private int nextId = 1;
+
+ @Override
+ public int applyAsInt(FeatureStructure aT) {
+ return nextId++;
}
}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/ShortTypeRefGenerator.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/ShortTypeRefGenerator.java
new file mode 100644
index 0000000..95ea7b1
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ref/ShortTypeRefGenerator.java
@@ -0,0 +1,46 @@
+/*
+ * 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.jsoncas2.ref;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.apache.uima.cas.Type;
+
+public class ShortTypeRefGenerator implements Function<Type, String> {
+ private Set<String> usedNames = new HashSet<>();
+
+ @Override
+ public String apply(Type aType) {
+ if (!usedNames.contains(aType.getShortName())) {
+ usedNames.add(aType.getShortName());
+ return aType.getShortName();
+ }
+
+ int n = 1;
+ String newName;
+ while (usedNames.contains(newName = aType.getShortName() + "-" + n)) {
+ n++;
+ }
+
+ usedNames.add(newName);
+ return newName;
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CasDeserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CasDeserializer.java
new file mode 100644
index 0000000..d0411ae
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CasDeserializer.java
@@ -0,0 +1,140 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static com.fasterxml.jackson.core.JsonTokenId.ID_END_ARRAY;
+import static com.fasterxml.jackson.core.JsonTokenId.ID_END_OBJECT;
+import static com.fasterxml.jackson.core.JsonTokenId.ID_START_OBJECT;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.FEATURE_STRUCTURES_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.HEADER_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.TYPES_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.VIEWS_FIELD;
+
+import java.io.IOException;
+import java.util.Map.Entry;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+import org.apache.uima.json.jsoncas2.model.FeatureStructures;
+import org.apache.uima.json.jsoncas2.model.Header;
+import org.apache.uima.json.jsoncas2.model.Views;
+import org.apache.uima.json.jsoncas2.ref.FeatureStructureIdToViewIndex;
+import org.apache.uima.json.jsoncas2.ref.FeatureStructureToIdIndex;
+import org.apache.uima.resource.ResourceInitializationException;
+import org.apache.uima.resource.metadata.TypeSystemDescription;
+import org.apache.uima.util.CasCreationUtils;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+
+public class CasDeserializer extends CasDeserializer_ImplBase<CAS> {
+ private static final long serialVersionUID = -5937326876753347248L;
+
+ public CasDeserializer() {
+ super(CAS.class);
+ }
+
+ @Override
+ public CAS deserialize(JsonParser aParser, DeserializationContext aCtxt)
+ throws IOException, JsonProcessingException {
+
+ FeatureStructureIdToViewIndex.set(aCtxt, new FeatureStructureIdToViewIndex());
+ FeatureStructureToIdIndex.set(aCtxt, new FeatureStructureToIdIndex());
+
+ boolean isFirst = true;
+ CAS cas = null;
+ TypeSystemDescription types = null;
+
+ while (aParser.currentToken() != null) {
+ if (isFirst) {
+ isFirst = false;
+ switch (aParser.currentTokenId()) {
+ // case ID_STRING:
+ // readDocumentText(aCas, parser.getValueAsString());
+ // return;
+ // case ID_START_ARRAY:
+ // jfses = readFeatureStructuresAsArray(parser);
+ // return;
+ case ID_START_OBJECT:
+ // In this case, we continue in the main loop and process the type system and
+ // feature structures if we find them.
+ aParser.nextFieldName();
+ break;
+ default:
+ throw new IOException("JSON must start with an object, array or string value, but was ["
+ + aParser.currentTokenId() + "]");
+ }
+ }
+
+ if (aParser.currentTokenId() == ID_END_ARRAY || aParser.currentTokenId() == ID_END_OBJECT) {
+ break;
+ }
+
+ // If we get here, we are operating on an object-type representation of the full CAS
+ switch (aParser.getCurrentName()) {
+ case HEADER_FIELD: {
+ aParser.nextValue();
+ Header header = aCtxt.readValue(aParser, Header.class);
+ OffsetConversionMode.set(aCtxt, header.getOffsetEncoding());
+ aParser.nextToken();
+ break;
+ }
+ case TYPES_FIELD:
+ aParser.nextValue();
+ types = aCtxt.readValue(aParser, TypeSystemDescription.class);
+ aParser.nextValue();
+ cas = createCasOrGetFromContext(aCtxt, types);
+ break;
+ case VIEWS_FIELD:
+ aCtxt.readValue(aParser, Views.class);
+ break;
+ case FEATURE_STRUCTURES_FIELD:
+ aCtxt.readValue(aParser, FeatureStructures.class);
+ break;
+ }
+ }
+
+ // Index FS in the respective views
+ FeatureStructureIdToViewIndex fsIdToViewIndex = FeatureStructureIdToViewIndex.get(aCtxt);
+ for (Entry<Integer, FeatureStructure> fsEntry : FeatureStructureToIdIndex.get(aCtxt)
+ .getAllFeatureStructures()) {
+ for (String viewName : fsIdToViewIndex.getViewsContainingFs(fsEntry.getKey())) {
+ cas.getView(viewName).addFsToIndexes(fsEntry.getValue());
+ }
+ }
+
+ return cas;
+ }
+
+ private CAS createCasOrGetFromContext(DeserializationContext aCtxt, TypeSystemDescription aTypes)
+ throws IOException {
+ CAS cas = getCas(aCtxt);
+ if (cas != null) {
+ return cas;
+ }
+
+ try {
+ return CasCreationUtils.createCas();
+ } catch (ResourceInitializationException e) {
+ throw new IOException(e);
+ }
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CasDeserializer_ImplBase.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CasDeserializer_ImplBase.java
new file mode 100644
index 0000000..fa7f908
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CasDeserializer_ImplBase.java
@@ -0,0 +1,89 @@
+/*
+ * 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.jsoncas2.ser;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.CASRuntimeException;
+
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+public abstract class CasDeserializer_ImplBase<T> extends StdDeserializer<T> {
+
+ private static final long serialVersionUID = -1269025453230384321L;
+
+ public static final String CONTEXT_CAS = "UIMA.CAS";
+ public static final String CONTEXT_POST_PROCESSORS = "UIMA.PostProcessors";
+ public static final String CONTEXT_DOCUMENT_ANNOTATION_READ_FLAG = "UIMA.DocumentAnnotatonRead";
+
+ protected CasDeserializer_ImplBase(Class<T> aVc) {
+ super(aVc);
+ }
+
+ protected CAS getCas(DeserializationContext aCtxt) {
+ return (CAS) aCtxt.getAttribute(CONTEXT_CAS);
+ }
+
+ protected void schedulePostprocessing(DeserializationContext aCtxt, Runnable aAction) {
+ List<Runnable> postProcessors = (List<Runnable>) aCtxt.getAttribute(CONTEXT_POST_PROCESSORS);
+ if (postProcessors == null) {
+ postProcessors = new ArrayList<>();
+ aCtxt.setAttribute(CONTEXT_POST_PROCESSORS, postProcessors);
+ }
+
+ postProcessors.add(aAction);
+ }
+
+ protected void runPostprocessors(DeserializationContext aCtxt) {
+ List<Runnable> postProcessors = (List<Runnable>) aCtxt.getAttribute(CONTEXT_POST_PROCESSORS);
+ if (postProcessors != null) {
+ postProcessors.forEach(Runnable::run);
+ }
+ }
+
+ protected void markDocumentAnnotationCreated(DeserializationContext aCtxt, String aView) {
+ documentAnnotationCreatedFlags(aCtxt).add(aView);
+ }
+
+ protected boolean isDocumentAnnotationCreated(DeserializationContext aCtxt, String aView) {
+ return documentAnnotationCreatedFlags(aCtxt).contains(aView);
+ }
+
+ private Set<String> documentAnnotationCreatedFlags(DeserializationContext aCtxt) {
+ Set<String> flags = (Set<String>) aCtxt.getAttribute(CONTEXT_DOCUMENT_ANNOTATION_READ_FLAG);
+ if (flags == null) {
+ flags = new HashSet<>();
+ aCtxt.setAttribute(CONTEXT_DOCUMENT_ANNOTATION_READ_FLAG, flags);
+ }
+ return flags;
+ }
+
+ protected CAS createOrGetView(CAS aCas, String aViewName) {
+ try {
+ return aCas.getView(aViewName);
+ } catch (CASRuntimeException e) {
+ return aCas.createView(aViewName);
+ }
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CasSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CasSerializer.java
new file mode 100644
index 0000000..a5ebb10
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CasSerializer.java
@@ -0,0 +1,127 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.FEATURE_STRUCTURES_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.HEADER_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.TYPES_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.VIEWS_FIELD;
+
+import java.io.IOException;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.impl.CASImpl;
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+import org.apache.uima.json.jsoncas2.mode.TypeSystemMode;
+import org.apache.uima.json.jsoncas2.model.FeatureStructures;
+import org.apache.uima.json.jsoncas2.model.Header;
+import org.apache.uima.json.jsoncas2.model.Views;
+import org.apache.uima.json.jsoncas2.ref.FeatureStructureToViewIndex;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class CasSerializer extends StdSerializer<CAS> {
+ private static final long serialVersionUID = 6779774576723692343L;
+
+ private final Supplier<ReferenceCache> refCacheSupplier;
+
+ public CasSerializer() {
+ this(ReferenceCache.builder()::build);
+ }
+
+ public CasSerializer(Supplier<ReferenceCache> aRefCacheSupplier) {
+ super(CAS.class);
+ refCacheSupplier = aRefCacheSupplier;
+ }
+
+ @Override
+ public void serialize(CAS aCas, JsonGenerator aJg, SerializerProvider aProvider)
+ throws IOException {
+ ReferenceCache.set(aProvider, refCacheSupplier.get());
+
+ initOffsetConversion(aCas, aProvider);
+
+ aJg.writeStartObject(aCas);
+
+ serializeHeader(aCas, aJg, aProvider);
+
+ serializeTypes(aCas, aJg, aProvider);
+
+ serializeFeatureStructures(aCas, aJg, aProvider);
+
+ serializeViews(aCas, aJg, aProvider);
+
+ aJg.writeEndObject();
+ }
+
+ private void serializeHeader(CAS aCas, JsonGenerator aJg, SerializerProvider aProvider)
+ throws IOException {
+ Header header = new Header(aProvider);
+ if (header.requiresSerialization()) {
+ aJg.writeFieldName(HEADER_FIELD);
+ aProvider.defaultSerializeValue(header, aJg);
+ }
+ }
+
+ private void serializeTypes(CAS aCas, JsonGenerator aJg, SerializerProvider aProvider)
+ throws IOException {
+ if (TypeSystemMode.get(aProvider) != TypeSystemMode.NONE) {
+ aJg.writeFieldName(TYPES_FIELD);
+ aProvider.defaultSerializeValue(aCas.getTypeSystem(), aJg);
+ }
+ }
+
+ private void serializeFeatureStructures(CAS aCas, JsonGenerator aJg, SerializerProvider aProvider)
+ throws IOException {
+ FeatureStructures allFSes = findAllFeatureStructures(aCas);
+ FeatureStructureToViewIndex.set(aProvider, new FeatureStructureToViewIndex(allFSes));
+ if (!allFSes.isEmpty()) {
+ aJg.writeFieldName(FEATURE_STRUCTURES_FIELD);
+ aProvider.defaultSerializeValue(allFSes, aJg);
+ }
+ }
+
+ private void serializeViews(CAS aCas, JsonGenerator aJg, SerializerProvider aProvider)
+ throws IOException {
+ Views views = new Views(aCas);
+ if (!views.isEmpty()) {
+ aJg.writeFieldName(VIEWS_FIELD);
+ aProvider.defaultSerializeValue(views, aJg);
+ }
+ }
+
+ private void initOffsetConversion(CAS aCas, SerializerProvider aProvider) {
+ for (CAS view : new Views(aCas)) {
+ OffsetConversionMode.initConverter(aProvider, view.getViewName(), view.getDocumentText());
+ }
+ }
+
+ private FeatureStructures findAllFeatureStructures(CAS aCas) {
+ Set<FeatureStructure> allFSes = new LinkedHashSet<>();
+ ((CASImpl) aCas).walkReachablePlusFSsSorted(allFSes::add, null, null, null);
+ return new FeatureStructures(allFSes);
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CommonArrayFSSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CommonArrayFSSerializer.java
new file mode 100644
index 0000000..03980e4
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/CommonArrayFSSerializer.java
@@ -0,0 +1,119 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.ELEMENTS_FIELD;
+
+import java.io.IOException;
+
+import org.apache.uima.cas.BooleanArrayFS;
+import org.apache.uima.cas.ByteArrayFS;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.CommonArrayFS;
+import org.apache.uima.cas.DoubleArrayFS;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.FloatArrayFS;
+import org.apache.uima.cas.IntArrayFS;
+import org.apache.uima.cas.LongArrayFS;
+import org.apache.uima.cas.ShortArrayFS;
+import org.apache.uima.cas.StringArrayFS;
+import org.apache.uima.jcas.cas.FSArray;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+@SuppressWarnings("rawtypes")
+public class CommonArrayFSSerializer extends FeatureStructureSerializer_ImplBase<CommonArrayFS> {
+ private static final long serialVersionUID = 4842019532480552884L;
+
+ public CommonArrayFSSerializer() {
+ super(CommonArrayFS.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void writeBody(SerializerProvider aProvider, JsonGenerator aJg, FeatureStructure aFs)
+ throws IOException {
+ aJg.writeFieldName(ELEMENTS_FIELD);
+ switch (aFs.getType().getName()) {
+ case CAS.TYPE_NAME_BOOLEAN_ARRAY: {
+ aJg.writeStartArray();
+ boolean[] values = ((BooleanArrayFS) aFs).toArray();
+ for (int i = 0; i < values.length; i++) {
+ aJg.writeBoolean(values[i]);
+ }
+ aJg.writeEndArray();
+ break;
+ }
+ case CAS.TYPE_NAME_BYTE_ARRAY: {
+ aJg.writeBinary(((ByteArrayFS) aFs).toArray());
+ break;
+ }
+ case CAS.TYPE_NAME_DOUBLE_ARRAY: {
+ double[] values = ((DoubleArrayFS) aFs).toArray();
+ aJg.writeArray(values, 0, values.length);
+ break;
+ }
+ case CAS.TYPE_NAME_FLOAT_ARRAY: {
+ float[] fValues = ((FloatArrayFS) aFs).toArray();
+ double[] dValues = new double[fValues.length];
+ for (int i = 0; i < fValues.length; i++) {
+ dValues[i] = fValues[i];
+ }
+ aJg.writeArray(dValues, 0, dValues.length);
+ break;
+ }
+ case CAS.TYPE_NAME_INTEGER_ARRAY: {
+ int[] values = ((IntArrayFS) aFs).toArray();
+ aJg.writeArray(values, 0, values.length);
+ break;
+ }
+ case CAS.TYPE_NAME_LONG_ARRAY: {
+ long[] values = ((LongArrayFS) aFs).toArray();
+ aJg.writeArray(values, 0, values.length);
+ break;
+ }
+ case CAS.TYPE_NAME_SHORT_ARRAY: {
+ short[] sValues = ((ShortArrayFS) aFs).toArray();
+ int[] iValues = new int[sValues.length];
+ for (int i = 0; i < sValues.length; i++) {
+ iValues[i] = sValues[i];
+ }
+ aJg.writeArray(iValues, 0, iValues.length);
+ break;
+ }
+ case CAS.TYPE_NAME_STRING_ARRAY: {
+ String[] values = ((StringArrayFS) aFs).toArray();
+ aJg.writeArray(values, 0, values.length);
+ break;
+ }
+ case CAS.TYPE_NAME_FS_ARRAY: // fall-through
+ default: {
+ aJg.writeStartArray();
+ ReferenceCache refCache = ReferenceCache.get(aProvider);
+ for (FeatureStructure fs : ((FSArray<FeatureStructure>) aFs)) {
+ aJg.writeNumber(refCache.fsRef(fs));
+ }
+ aJg.writeEndArray();
+ break;
+ }
+ }
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureDeserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureDeserializer.java
new file mode 100644
index 0000000..b615149
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureDeserializer.java
@@ -0,0 +1,75 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.ARRAY_SUFFIX;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.ELEMENT_TYPE_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NAME_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.RANGE_FIELD;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.apache.uima.UIMAFramework;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.resource.metadata.FeatureDescription;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class FeatureDeserializer extends CasDeserializer_ImplBase<FeatureDescription> {
+ private static final long serialVersionUID = 1L;
+
+ public FeatureDeserializer() {
+ super(FeatureDescription.class);
+ }
+
+ @Override
+ public FeatureDescription deserialize(JsonParser aParser, DeserializationContext aCtxt)
+ throws IOException, JsonProcessingException {
+ JsonNode node = aParser.readValueAsTree();
+
+ if (!node.isObject()) {
+ throw new JsonParseException(aParser, "Feature declaration must be a JSON object");
+ }
+
+ String featureName = node.get(NAME_FIELD).asText();
+ String featureRangeType = node.get(RANGE_FIELD).asText();
+ Optional<String> componentType;
+ if (featureRangeType.endsWith(ARRAY_SUFFIX)) {
+ componentType = Optional
+ .of(featureRangeType.substring(0, featureRangeType.length() - ARRAY_SUFFIX.length()));
+ featureRangeType = CAS.TYPE_NAME_FS_ARRAY;
+ } else {
+ componentType = Optional.ofNullable(node.get(ELEMENT_TYPE_FIELD)).map(JsonNode::asText);
+ }
+
+ FeatureDescription fd = UIMAFramework.getResourceSpecifierFactory().createFeatureDescription();
+ fd.setName(featureName);
+ fd.setRangeTypeName(featureRangeType);
+ fd.setElementType(componentType.orElse(null));
+ // fd.setMultipleReferencesAllowed();
+ // fd.setDescription("");
+ // td.setSourceUrl(aParser.getTokenLocation().sourceDescription());
+ return fd;
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureSerializer.java
new file mode 100644
index 0000000..3df1159
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureSerializer.java
@@ -0,0 +1,116 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static org.apache.uima.cas.CAS.TYPE_NAME_FS_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_TOP;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.ARRAY_SUFFIX;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.ELEMENT_TYPE_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NAME_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.RANGE_FIELD;
+import static org.apache.uima.json.jsoncas2.mode.ArrayTypeMode.AS_ARRAY_TYPED_RANGE;
+
+import java.io.IOException;
+
+import org.apache.uima.cas.Feature;
+import org.apache.uima.cas.Type;
+import org.apache.uima.json.jsoncas2.mode.ArrayTypeMode;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class FeatureSerializer extends StdSerializer<Feature> {
+ private static final long serialVersionUID = 6706550270922386356L;
+
+ private ArrayTypeMode arrayMode = AS_ARRAY_TYPED_RANGE;
+
+ public FeatureSerializer() {
+ super(Feature.class);
+ }
+
+ public void setArrayMode(ArrayTypeMode aArrayMode) {
+ arrayMode = aArrayMode;
+ }
+
+ public ArrayTypeMode getArrayMode() {
+ return arrayMode;
+ }
+
+ @Override
+ public void serialize(Feature aFeature, JsonGenerator jg, SerializerProvider aProvider)
+ throws IOException {
+ ReferenceCache refCache = ReferenceCache.get(aProvider);
+
+ jg.writeStartObject(aFeature);
+
+ jg.writeStringField(NAME_FIELD, aFeature.getShortName());
+
+ // special check for array range types, which are represented in the CAS as
+ // elementType[] but in the descriptor as an FSArray with an <elementType>
+ Type rangeType = aFeature.getRange();
+ if (rangeType.isArray() && !rangeType.getComponentType().isPrimitive()) {
+ switch (arrayMode) {
+ case AS_ARRAY_TYPED_RANGE:
+ serializeArrayFieldAsRangeWithArrayMarker(refCache, jg, aFeature);
+ break;
+ case AS_RANGE_AND_ELEMENT:
+ serializeArrayFieldAsTypeAndElementType(refCache, jg, aFeature);
+ break;
+ }
+ } else {
+ serializeField(refCache, jg, aFeature);
+ }
+
+ jg.writeEndObject();
+ }
+
+ private void serializeField(ReferenceCache refCache, JsonGenerator jg, Feature aFeature)
+ throws IOException {
+ jg.writeStringField(RANGE_FIELD, refCache.typeRef(aFeature.getRange()));
+ Type componentType = aFeature.getRange().getComponentType();
+ if (componentType != null) {
+ jg.writeStringField(ELEMENT_TYPE_FIELD, refCache.typeRef(componentType));
+ }
+ }
+
+ /**
+ * @see ArrayTypeMode#AS_ARRAY_TYPED_RANGE
+ */
+ private void serializeArrayFieldAsRangeWithArrayMarker(ReferenceCache refCache, JsonGenerator jg,
+ Feature aFeature) throws IOException {
+ jg.writeStringField(RANGE_FIELD,
+ refCache.typeRef(aFeature.getRange().getComponentType()) + ARRAY_SUFFIX);
+ }
+
+ /**
+ * @see ArrayTypeMode#AS_RANGE_AND_ELEMENT
+ */
+ private void serializeArrayFieldAsTypeAndElementType(ReferenceCache refCache, JsonGenerator jg,
+ Feature aFeature) throws IOException {
+ Type rangeType = aFeature.getRange();
+ jg.writeStringField(RANGE_FIELD, TYPE_NAME_FS_ARRAY);
+
+ // Component type can be omitted if it is the default (TOP)
+ if (!TYPE_NAME_TOP.equals(rangeType.getComponentType().getName())) {
+ jg.writeStringField(ELEMENT_TYPE_FIELD, refCache.typeRef(rangeType.getComponentType()));
+ }
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructureDeserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructureDeserializer.java
new file mode 100644
index 0000000..494d67e
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructureDeserializer.java
@@ -0,0 +1,658 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static com.fasterxml.jackson.core.JsonToken.END_OBJECT;
+import static com.fasterxml.jackson.core.JsonToken.START_OBJECT;
+import static java.lang.Integer.MIN_VALUE;
+import static org.apache.uima.cas.CAS.FEATURE_BASE_NAME_SOFAARRAY;
+import static org.apache.uima.cas.CAS.FEATURE_BASE_NAME_SOFAID;
+import static org.apache.uima.cas.CAS.FEATURE_BASE_NAME_SOFAMIME;
+import static org.apache.uima.cas.CAS.FEATURE_BASE_NAME_SOFANUM;
+import static org.apache.uima.cas.CAS.FEATURE_BASE_NAME_SOFASTRING;
+import static org.apache.uima.cas.CAS.FEATURE_BASE_NAME_SOFAURI;
+import static org.apache.uima.cas.CAS.TYPE_NAME_BOOLEAN_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_BYTE;
+import static org.apache.uima.cas.CAS.TYPE_NAME_BYTE_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_DOCUMENT_ANNOTATION;
+import static org.apache.uima.cas.CAS.TYPE_NAME_DOUBLE;
+import static org.apache.uima.cas.CAS.TYPE_NAME_DOUBLE_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_FLOAT;
+import static org.apache.uima.cas.CAS.TYPE_NAME_FLOAT_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_INTEGER;
+import static org.apache.uima.cas.CAS.TYPE_NAME_INTEGER_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_LONG;
+import static org.apache.uima.cas.CAS.TYPE_NAME_LONG_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_SHORT;
+import static org.apache.uima.cas.CAS.TYPE_NAME_SHORT_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_SOFA;
+import static org.apache.uima.cas.CAS.TYPE_NAME_STRING_ARRAY;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.ANCHOR_FEATURE_PREFIX;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.ID_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMBER_FLOAT_NAN;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMBER_FLOAT_NEGATIVE_INFINITY;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMBER_FLOAT_NEGATIVE_INFINITY_ABBR;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMBER_FLOAT_POSITIVE_INFINITY;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMBER_FLOAT_POSITIVE_INFINITY_ABBR;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMERIC_FEATURE_PREFIX;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.REF_FEATURE_PREFIX;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.RESERVED_FIELD_PREFIX;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.TYPE_FIELD;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+import org.apache.uima.cas.ArrayFS;
+import org.apache.uima.cas.BooleanArrayFS;
+import org.apache.uima.cas.ByteArrayFS;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.CASRuntimeException;
+import org.apache.uima.cas.DoubleArrayFS;
+import org.apache.uima.cas.Feature;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.FloatArrayFS;
+import org.apache.uima.cas.IntArrayFS;
+import org.apache.uima.cas.LongArrayFS;
+import org.apache.uima.cas.ShortArrayFS;
+import org.apache.uima.cas.SofaFS;
+import org.apache.uima.cas.StringArrayFS;
+import org.apache.uima.cas.Type;
+import org.apache.uima.cas.TypeSystem;
+import org.apache.uima.cas.impl.CASImpl;
+import org.apache.uima.jcas.cas.TOP;
+import org.apache.uima.jcas.tcas.Annotation;
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+import org.apache.uima.json.jsoncas2.ref.FeatureStructureToIdIndex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+
+public class FeatureStructureDeserializer extends CasDeserializer_ImplBase<FeatureStructure> {
+ private static final long serialVersionUID = -5937326876753347248L;
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private enum FieldType {
+ REGULAR, REFERENCE, NUMBER, ANCHOR
+ }
+
+ public FeatureStructureDeserializer() {
+ super(FeatureStructure.class);
+ }
+
+ @Override
+ public FeatureStructure deserialize(JsonParser aParser, DeserializationContext aCtxt)
+ throws IOException, JsonProcessingException {
+
+ CAS cas = getCas(aCtxt);
+
+ if (aParser.currentToken() != START_OBJECT) {
+ throw new JsonParseException(aParser, "Expected feature structure to start with "
+ + START_OBJECT + " but found " + aParser.currentToken() + " instead");
+ }
+
+ int fsId = MIN_VALUE;
+ // Handle case where feature structures section is represented as a map instead of an array
+ if (aParser.getCurrentName() != null) {
+ fsId = Integer.parseInt(aParser.getCurrentName());
+ }
+
+ FeatureStructure fs = null;
+ aParser.nextValue();
+ while (aParser.currentToken() != END_OBJECT) {
+ String fieldName = aParser.currentName();
+
+ // log.trace("Deserializing {}: {}", fieldName, aParser.getText());
+
+ if (fieldName.startsWith(RESERVED_FIELD_PREFIX)) {
+ switch (fieldName) {
+ case ID_FIELD:
+ // Handle case where feature structures section is represented as an array
+ fsId = aParser.getIntValue();
+ break;
+ case TYPE_FIELD:
+ if (fsId == MIN_VALUE) {
+ throw new JsonParseException(aParser, TYPE_FIELD + " must come after " + ID_FIELD);
+ }
+ String typeName = aParser.getValueAsString();
+
+ switch (typeName) {
+ case TYPE_NAME_BOOLEAN_ARRAY:
+ fs = deserializeBooleanArray(aParser, cas);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ case TYPE_NAME_BYTE_ARRAY:
+ fs = deserializeByteArray(aParser, cas);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ case TYPE_NAME_DOUBLE_ARRAY:
+ fs = deserializeDoubleArray(aParser, cas);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ case TYPE_NAME_FLOAT_ARRAY:
+ fs = deserializeFloatArray(aParser, cas);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ case TYPE_NAME_INTEGER_ARRAY:
+ fs = deserializeIntegerArray(aParser, cas);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ case TYPE_NAME_LONG_ARRAY:
+ fs = deserializeLongArray(aParser, cas);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ case TYPE_NAME_SHORT_ARRAY:
+ fs = deserializeShortArray(aParser, cas);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ case TYPE_NAME_STRING_ARRAY:
+ fs = deserializeStringArray(aParser, cas);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ case CAS.TYPE_NAME_FS_ARRAY:
+ fs = deserializeFsArray(aParser, cas, aCtxt);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ case TYPE_NAME_SOFA:
+ fs = createSofaFS(cas, aParser, aCtxt);
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+ continue;
+ default:
+ fs = createFS(aParser, aCtxt, fsId, cas);
+ break;
+ }
+ break;
+ // case FLAGS_FIELD:
+ // // FIXME: We probably don't need the flags field at all.
+ // aParser.nextToken();
+ // aParser.skipChildren();
+ // aParser.nextToken();
+ // break;
+ }
+
+ aParser.nextValue();
+ continue;
+ }
+
+ if (fs == null || fsId == MIN_VALUE) {
+ throw new JsonParseException(aParser,
+ "Features must come after " + ID_FIELD + "" + TYPE_FIELD);
+ }
+
+ FieldType fieldType = FieldType.REGULAR;
+ if (fieldName.startsWith(REF_FEATURE_PREFIX)) {
+ fieldName = fieldName.substring(REF_FEATURE_PREFIX.length());
+ fieldType = FieldType.REFERENCE;
+ } else if (fieldName.startsWith(NUMERIC_FEATURE_PREFIX)) {
+ fieldName = fieldName.substring(NUMERIC_FEATURE_PREFIX.length());
+ fieldType = FieldType.NUMBER;
+ } else if (fieldName.startsWith(ANCHOR_FEATURE_PREFIX)) {
+ fieldName = fieldName.substring(ANCHOR_FEATURE_PREFIX.length());
+ fieldType = FieldType.ANCHOR;
+ }
+
+ if (CAS.FEATURE_FULL_NAME_SOFA
+ .equals(fs.getType().getFeatureByBaseName(fieldName).getName())) {
+ // Ignore the SofA feature of AnnotationBase-derived types - this feature cannot be set
+ // manually - this happens (hopefully) when adding the AnnotationBase FS to the indexes of
+ // the particular SofA.
+ aParser.nextValue();
+ continue;
+ }
+
+ if (fieldType == FieldType.REFERENCE) {
+ deserializeFsReference(aParser, aCtxt, fs, fieldName);
+ aParser.nextValue();
+ continue;
+ }
+
+ deserializePrimitive(aParser, aCtxt, fs, fieldName, fieldType);
+ aParser.nextValue();
+ }
+
+ // Special handling of the document annotation
+ handleDocumentAnnotation(aCtxt, cas, fs);
+
+ // Register the loaded FS
+ FeatureStructureToIdIndex.get(aCtxt).put(fsId, fs);
+
+ return fs;
+ }
+
+ private FeatureStructure createFS(JsonParser aParser, DeserializationContext aCtxt, int aFsId,
+ CAS aCas) throws IOException {
+ String typeName = aParser.getValueAsString();
+ TypeSystem ts = aCas.getTypeSystem();
+ Type t = ts.getType(typeName);
+ if (t == null) {
+ throw new JsonParseException(aParser, "Type not found in type system: " + typeName);
+ }
+
+ return aCas.createFS(t);
+ }
+
+ // Case 1: there is no document annotation yet (unlikely since the document text has probably
+ // been set already and this implicitly triggers the creation of a document annotation)
+ // -> the document annotation that was created became the primary document annotation,
+ // nothing else to do
+ //
+ // Case 2: there is already a document annotation of the same type as what we deserialize
+ // 2a> if it is the first time we deserialize a document annotation, then fill it
+ // 2b> otherwise add the new document annotation
+ //
+ // Case 3: there is already a document annotation but is has a different type
+ // 3a> if it is the first time we deserialize a document annotation, then replace it
+ // 3b> otherwise add the new document annotation
+ private void handleDocumentAnnotation(DeserializationContext aCtxt, CAS aCas,
+ FeatureStructure aFS) throws JsonParseException {
+ TypeSystem ts = aCas.getTypeSystem();
+ Type docAnnoType = ts.getType(TYPE_NAME_DOCUMENT_ANNOTATION);
+ if (!ts.subsumes(docAnnoType, aFS.getType())) {
+ return;
+ }
+
+ String viewName = ((Annotation) aFS).getSofa().getSofaID();
+
+ // Case 2b/3b: we already have handled the primary document annotation, this one is an extra
+ if (isDocumentAnnotationCreated(aCtxt, viewName)) {
+ // Nothing extra to do
+ return;
+ }
+
+ Collection<TOP> docAnnotations = aCas.getIndexedFSs(docAnnoType);
+ // Case 1: was no document annotation yet
+ if (docAnnotations.isEmpty()) {
+ markDocumentAnnotationCreated(aCtxt, viewName);
+ return;
+ }
+
+ // Case 3a: need to replace the existing document annotation because it has a different type
+ docAnnotations.forEach(aCas::removeFsFromIndexes);
+ aCas.addFsToIndexes(aFS);
+ markDocumentAnnotationCreated(aCtxt, viewName);
+ }
+
+ /**
+ * Parses SofaFS entries from the feature structure list. If no data has been set in the SofaFS,
+ * this method will return null.
+ */
+ private FeatureStructure createSofaFS(CAS aCas, JsonParser aParser, DeserializationContext aCtxt)
+ throws IOException {
+ int sofaNum = -1;
+ String sofaID = null;
+ String mimeType = null;
+ String sofaURI = null;
+ String sofaString = null;
+ FeatureStructure sofaArray = null;
+
+ aParser.nextValue();
+ while (aParser.currentToken() != JsonToken.END_OBJECT) {
+ String fieldName = aParser.currentName();
+
+ switch (fieldName) {
+ case FEATURE_BASE_NAME_SOFANUM:
+ sofaNum = aParser.getValueAsInt();
+ break;
+ case FEATURE_BASE_NAME_SOFAID:
+ sofaID = aParser.getValueAsString();
+ break;
+ case FEATURE_BASE_NAME_SOFAMIME:
+ mimeType = aParser.getValueAsString();
+ break;
+ case FEATURE_BASE_NAME_SOFAURI:
+ sofaURI = aParser.getValueAsString();
+ break;
+ case FEATURE_BASE_NAME_SOFASTRING:
+ sofaString = aParser.getValueAsString();
+ break;
+ case REF_FEATURE_PREFIX + FEATURE_BASE_NAME_SOFAARRAY: {
+ FeatureStructureToIdIndex fsIdx = FeatureStructureToIdIndex.get(aCtxt);
+ int sofaArrayId = aParser.getValueAsInt();
+ sofaArray = fsIdx.get(sofaArrayId)
+ .orElseThrow(() -> new JsonParseException(aParser, "The SofA array with ID "
+ + sofaArrayId + " must come before the SofAFS referencing it."));
+ break;
+ }
+ default:
+ throw new JsonParseException(aParser, "Unexpeced field in SofA: " + fieldName);
+ }
+
+ aParser.nextValue();
+ }
+
+ if (sofaID == null) {
+ throw new JsonParseException(aParser, "SofA must have a sofaID");
+ }
+
+ CAS view = createOrGetView(aCas, sofaID);
+
+ if (sofaURI != null) {
+ view.setSofaDataURI(sofaURI, mimeType);
+ } else if (sofaString != null) {
+ view.setSofaDataString(sofaString, mimeType);
+ OffsetConversionMode.initConverter(aCtxt, sofaID, sofaString);
+ } else if (sofaArray != null) {
+ view.setSofaDataArray(sofaArray, mimeType);
+ }
+
+ SofaFS sofa = view.getSofa();
+ if (sofa != null) {
+ return sofa;
+ }
+
+ return ((CASImpl) view).getSofaRef();
+ }
+
+ private BooleanArrayFS deserializeBooleanArray(JsonParser aParser, CAS aCas) throws IOException {
+ // Skip array opening and go to first value (or end of array if there is no value)
+ aParser.nextValue();
+ aParser.nextValue();
+ List<Boolean> values = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ values.add(aParser.getBooleanValue());
+ aParser.nextValue();
+ }
+ BooleanArrayFS arrayFs = aCas.createBooleanArrayFS(values.size());
+ for (int i = 0; i < values.size(); i++) {
+ arrayFs.set(i, values.get(i));
+ }
+ return arrayFs;
+ }
+
+ private ByteArrayFS deserializeByteArray(JsonParser aParser, CAS aCas) throws IOException {
+ aParser.nextValue();
+ byte[] bytes = aParser.getBinaryValue();
+ ByteArrayFS arrayFs = aCas.createByteArrayFS(bytes.length);
+ arrayFs.copyFromArray(bytes, 0, 0, bytes.length);
+ aParser.nextToken();
+ return arrayFs;
+ }
+
+ private DoubleArrayFS deserializeDoubleArray(JsonParser aParser, CAS aCas) throws IOException {
+ // Skip array opening and go to first value (or end of array if there is no value)
+ aParser.nextValue();
+ aParser.nextValue();
+ List<Double> values = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ values.add(readDoubleValue(aParser));
+ aParser.nextValue();
+ }
+ DoubleArrayFS arrayFs = aCas.createDoubleArrayFS(values.size());
+ for (int i = 0; i < values.size(); i++) {
+ arrayFs.set(i, values.get(i));
+ }
+ return arrayFs;
+ }
+
+ private FloatArrayFS deserializeFloatArray(JsonParser aParser, CAS aCas) throws IOException {
+ // Skip array opening and go to first value (or end of array if there is no value)
+ aParser.nextValue();
+ aParser.nextValue();
+ List<Float> values = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ values.add((float) readDoubleValue(aParser));
+ aParser.nextValue();
+ }
+ FloatArrayFS arrayFs = aCas.createFloatArrayFS(values.size());
+ for (int i = 0; i < values.size(); i++) {
+ arrayFs.set(i, values.get(i));
+ }
+ return arrayFs;
+ }
+
+ private double readDoubleValue(JsonParser aParser) throws IOException {
+ if (aParser.currentToken() == JsonToken.VALUE_NUMBER_FLOAT) {
+ return aParser.getDoubleValue();
+ }
+
+ if (aParser.currentToken() == JsonToken.VALUE_STRING) {
+ switch (aParser.getValueAsString()) {
+ case NUMBER_FLOAT_NAN:
+ return Double.NaN;
+ case NUMBER_FLOAT_POSITIVE_INFINITY:
+ case NUMBER_FLOAT_POSITIVE_INFINITY_ABBR:
+ return Double.POSITIVE_INFINITY;
+ case NUMBER_FLOAT_NEGATIVE_INFINITY:
+ case NUMBER_FLOAT_NEGATIVE_INFINITY_ABBR:
+ return Double.NEGATIVE_INFINITY;
+ default:
+ throw new JsonParseException(aParser,
+ "Expected special floating point value (NaN, -Inf, -Infinity, Inf, Infinity), "
+ + "but got [" + aParser.getValueAsString() + "]");
+ }
+ }
+
+ throw new JsonParseException(aParser,
+ "Expected floating point value as VALUE_NUMBER_FLOAT or VALUE_STRING for special "
+ + "values (Nan, -Infinity, +Infinity), but got [" + aParser.currentToken()
+ + "]");
+ }
+
+ private IntArrayFS deserializeIntegerArray(JsonParser aParser, CAS aCas) throws IOException {
+ // Skip array opening and go to first value (or end of array if there is no value)
+ aParser.nextValue();
+ aParser.nextValue();
+ List<Integer> values = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ values.add(aParser.getIntValue());
+ aParser.nextValue();
+ }
+ IntArrayFS arrayFs = aCas.createIntArrayFS(values.size());
+ for (int i = 0; i < values.size(); i++) {
+ arrayFs.set(i, values.get(i));
+ }
+ return arrayFs;
+ }
+
+ private LongArrayFS deserializeLongArray(JsonParser aParser, CAS aCas) throws IOException {
+ // Skip array opening and go to first value (or end of array if there is no value)
+ aParser.nextValue();
+ aParser.nextValue();
+ List<Long> values = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ values.add(aParser.getLongValue());
+ aParser.nextValue();
+ }
+ LongArrayFS arrayFs = aCas.createLongArrayFS(values.size());
+ for (int i = 0; i < values.size(); i++) {
+ arrayFs.set(i, values.get(i));
+ }
+ return arrayFs;
+ }
+
+ private ShortArrayFS deserializeShortArray(JsonParser aParser, CAS aCas) throws IOException {
+ // Skip array opening and go to first value (or end of array if there is no value)
+ aParser.nextValue();
+ aParser.nextValue();
+ List<Short> values = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ values.add((short) aParser.getIntValue());
+ aParser.nextValue();
+ }
+ ShortArrayFS arrayFs = aCas.createShortArrayFS(values.size());
+ for (int i = 0; i < values.size(); i++) {
+ arrayFs.set(i, values.get(i));
+ }
+ return arrayFs;
+ }
+
+ private StringArrayFS deserializeStringArray(JsonParser aParser, CAS aCas) throws IOException {
+ // Go to array opening
+ aParser.nextValue();
+ // Go to first value if any or to end of array
+ aParser.nextValue();
+ List<String> values = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ values.add(aParser.getValueAsString());
+ aParser.nextValue();
+ }
+ StringArrayFS arrayFs = aCas.createStringArrayFS(values.size());
+ for (int i = 0; i < values.size(); i++) {
+ arrayFs.set(i, values.get(i));
+ }
+ return arrayFs;
+ }
+
+ private ArrayFS<FeatureStructure> deserializeFsArray(JsonParser aParser, CAS aCas,
+ DeserializationContext aCtxt) throws IOException {
+ // Go to array opening
+ aParser.nextValue();
+ // Go to first value if any or to end of array
+ aParser.nextValue();
+ List<Integer> values = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ values.add(aParser.getIntValue());
+ aParser.nextValue();
+ }
+
+ @SuppressWarnings("unchecked")
+ ArrayFS<FeatureStructure> arrayFs = aCas.createArrayFS(values.size());
+ FeatureStructureToIdIndex idToFsIdx = FeatureStructureToIdIndex.get(aCtxt);
+ for (int i = 0; i < values.size(); i++) {
+ int targetFsId = values.get(i);
+ Optional<FeatureStructure> targetFs = idToFsIdx.get(targetFsId);
+ if (targetFs.isPresent()) {
+ arrayFs.set(i, targetFs.get());
+ } else {
+ int finalIndex = i;
+ schedulePostprocessing(aCtxt, () -> {
+ arrayFs.set(finalIndex,
+ idToFsIdx.get(targetFsId)
+ .orElseThrow(() -> new NoSuchElementException("Unable to resolve ID ["
+ + targetFsId + "] during array post-processing")));
+ });
+ }
+ }
+ return arrayFs;
+ }
+
+ private void deserializePrimitive(JsonParser aParser, DeserializationContext aCtxt,
+ FeatureStructure aFs, String aFeatureName, FieldType fieldType)
+ throws CASRuntimeException, IOException {
+ Feature feature = aFs.getType().getFeatureByBaseName(aFeatureName);
+
+ if (fieldType == FieldType.NUMBER) {
+ deserializeFloatingPointValue(aParser, aFs, feature);
+ return;
+ }
+
+ switch (aParser.currentToken()) {
+ case VALUE_NULL:
+ // No need do to anything really - we just leave the feature alone
+ break;
+ case VALUE_TRUE: // fall-through
+ case VALUE_FALSE:
+ aFs.setBooleanValue(feature, aParser.getBooleanValue());
+ break;
+ case VALUE_STRING:
+ aFs.setStringValue(feature, aParser.getValueAsString());
+ break;
+ case VALUE_NUMBER_FLOAT: // JSON does not distinguish between double and float
+ deserializeFloatingPointValue(aParser, aFs, feature);
+ break;
+ case VALUE_NUMBER_INT:
+ deserializeIntegerValue(aParser, aCtxt, aFs, feature, fieldType);
+ break;
+ default:
+ throw new JsonParseException(aParser,
+ "Expected a feature value as null, a boolean, string, or number but got "
+ + aParser.currentToken());
+ }
+ }
+
+ private void deserializeFsReference(JsonParser aParser, DeserializationContext aCtxt,
+ FeatureStructure aFs, String aFieldName) throws IOException {
+ FeatureStructureToIdIndex idToFsIdx = FeatureStructureToIdIndex.get(aCtxt);
+ int targetFsId = aParser.getIntValue();
+ Optional<FeatureStructure> targetFs = idToFsIdx.get(targetFsId);
+ Feature feature = aFs.getType().getFeatureByBaseName(aFieldName);
+ if (targetFs.isPresent()) {
+ aFs.setFeatureValue(feature, targetFs.get());
+ } else {
+ FeatureStructure finalFs = aFs;
+ schedulePostprocessing(aCtxt, () -> {
+ finalFs.setFeatureValue(feature,
+ idToFsIdx.get(targetFsId).orElseThrow(() -> new NoSuchElementException(
+ "Unable to resolve ID [" + targetFsId + "] during post-processing")));
+ });
+ }
+ }
+
+ private void deserializeFloatingPointValue(JsonParser aParser, FeatureStructure aFs,
+ Feature aFeature) throws CASRuntimeException, IOException {
+ switch (aFeature.getRange().getName()) {
+ case TYPE_NAME_DOUBLE:
+ aFs.setDoubleValue(aFeature, readDoubleValue(aParser));
+ break;
+ case TYPE_NAME_FLOAT:
+ aFs.setFloatValue(aFeature, (float) readDoubleValue(aParser));
+ break;
+ default:
+ throw new JsonParseException(aParser, "Feature of type " + aFeature.getRange().getName()
+ + " cannot be set from a JSON value of type " + aParser.currentToken());
+ }
+ }
+
+ private void deserializeIntegerValue(JsonParser aParser, DeserializationContext aCtxt,
+ FeatureStructure aFs, Feature aFeature, FieldType fieldType)
+ throws CASRuntimeException, IOException {
+ switch (aFeature.getRange().getName()) {
+ case TYPE_NAME_BYTE:
+ aFs.setByteValue(aFeature, (byte) aParser.getValueAsInt());
+ break;
+ case TYPE_NAME_INTEGER:
+ int value = aParser.getValueAsInt();
+ value = convertOffsetsIfNecessary(aCtxt, aFs, aFeature, value, fieldType);
+ aFs.setIntValue(aFeature, value);
+ break;
+ case TYPE_NAME_LONG:
+ aFs.setLongValue(aFeature, aParser.getValueAsLong());
+ break;
+ case TYPE_NAME_SHORT:
+ aFs.setShortValue(aFeature, (short) aParser.getValueAsInt());
+ break;
+ default:
+ throw new JsonParseException(aParser, "Feature of type " + aFeature.getRange().getName()
+ + " cannot be set from a JSON value of type " + aParser.currentToken());
+ }
+ }
+
+ private int convertOffsetsIfNecessary(DeserializationContext aCtxt, FeatureStructure aFs,
+ Feature aFeature, int aValue, FieldType fieldType) {
+ if (aFs instanceof Annotation && (CAS.FEATURE_FULL_NAME_BEGIN.equals(aFeature.getName())
+ || CAS.FEATURE_FULL_NAME_END.equals(aFeature.getName())
+ || fieldType == FieldType.ANCHOR)) {
+ Annotation ann = (Annotation) aFs;
+ return OffsetConversionMode.getConverter(aCtxt, ann.getSofa().getSofaID()) //
+ .map(conv -> conv.mapExternal(aValue)) //
+ .orElse(aValue);
+ }
+
+ return aValue;
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructureSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructureSerializer.java
new file mode 100644
index 0000000..bf29678
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructureSerializer.java
@@ -0,0 +1,138 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMBER_FLOAT_NAN;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMBER_FLOAT_NEGATIVE_INFINITY;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMBER_FLOAT_POSITIVE_INFINITY;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.NUMERIC_FEATURE_PREFIX;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.REF_FEATURE_PREFIX;
+
+import java.io.IOException;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Feature;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.jcas.tcas.Annotation;
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.DatabindContext;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+public class FeatureStructureSerializer
+ extends FeatureStructureSerializer_ImplBase<FeatureStructure> {
+ private static final long serialVersionUID = -5346232657650250679L;
+
+ public FeatureStructureSerializer() {
+ super(FeatureStructure.class);
+ }
+
+ protected FeatureStructureSerializer(Class<? extends FeatureStructure> aClazz) {
+ super((Class<FeatureStructure>) aClazz);
+ }
+
+ @Override
+ protected void writeBody(SerializerProvider aProvider, JsonGenerator aJg, FeatureStructure aFs)
+ throws IOException {
+ ReferenceCache refCache = ReferenceCache.get(aProvider);
+ for (Feature feature : aFs.getType().getFeatures()) {
+ writeFeature(aProvider, refCache, aJg, aFs, feature);
+ }
+ }
+
+ protected void writeFeature(SerializerProvider aProvider, ReferenceCache aRefCache,
+ JsonGenerator aJg, FeatureStructure aFs, Feature aFeature) throws IOException {
+ if (!aFeature.getRange().isPrimitive()) {
+ FeatureStructure target = aFs.getFeatureValue(aFeature);
+ if (target != null) {
+ aJg.writeNumberField(REF_FEATURE_PREFIX + aFeature.getShortName(),
+ aRefCache.fsRef(aFs.getFeatureValue(aFeature)));
+ }
+ return;
+ }
+
+ if (aFeature.getRange().isStringOrStringSubtype()) {
+ String value = aFs.getStringValue(aFeature);
+ if (value != null) {
+ aJg.writeStringField(aFeature.getShortName(), value);
+ }
+
+ return;
+ }
+
+ String rangeTypeName = aFeature.getRange().getName();
+ switch (rangeTypeName) {
+ case CAS.TYPE_NAME_BOOLEAN:
+ aJg.writeBooleanField(aFeature.getShortName(), aFs.getBooleanValue(aFeature));
+ break;
+ case CAS.TYPE_NAME_BYTE:
+ aJg.writeNumberField(aFeature.getShortName(), aFs.getByteValue(aFeature));
+ break;
+ case CAS.TYPE_NAME_DOUBLE:
+ writeFloatingPointField(aJg, aFeature.getShortName(), aFs.getDoubleValue(aFeature));
+ break;
+ case CAS.TYPE_NAME_FLOAT:
+ writeFloatingPointField(aJg, aFeature.getShortName(), aFs.getFloatValue(aFeature));
+ break;
+ case CAS.TYPE_NAME_INTEGER: {
+ aJg.writeFieldName(aFeature.getShortName());
+ int value = aFs.getIntValue(aFeature);
+ value = convertOffsetsIfNecessary(aProvider, aFs, aFeature, value);
+ aJg.writeNumber(value);
+ break;
+ }
+ case CAS.TYPE_NAME_LONG:
+ aJg.writeNumberField(aFeature.getShortName(), aFs.getLongValue(aFeature));
+ break;
+ case CAS.TYPE_NAME_SHORT:
+ aJg.writeNumberField(aFeature.getShortName(), aFs.getShortValue(aFeature));
+ break;
+ default:
+ throw new IOException("Unsupported primitive type [" + rangeTypeName + "]");
+ }
+ }
+
+ private void writeFloatingPointField(JsonGenerator aJg, String aFeatureName, double aValue)
+ throws IOException {
+ if (Double.isNaN(aValue)) {
+ aJg.writeStringField(NUMERIC_FEATURE_PREFIX + aFeatureName, NUMBER_FLOAT_NAN);
+ } else if (aValue == Double.NEGATIVE_INFINITY) {
+ aJg.writeStringField(NUMERIC_FEATURE_PREFIX + aFeatureName, NUMBER_FLOAT_NEGATIVE_INFINITY);
+ } else if (aValue == Double.POSITIVE_INFINITY) {
+ aJg.writeStringField(NUMERIC_FEATURE_PREFIX + aFeatureName, NUMBER_FLOAT_POSITIVE_INFINITY);
+ } else {
+ aJg.writeNumberField(aFeatureName, aValue);
+ }
+ }
+
+ private int convertOffsetsIfNecessary(DatabindContext aCtxt, FeatureStructure aFs,
+ Feature aFeature, int aValue) {
+ if (aFs instanceof Annotation && (CAS.FEATURE_FULL_NAME_BEGIN.equals(aFeature.getName())
+ || CAS.FEATURE_FULL_NAME_END.equals(aFeature.getName()))) {
+ Annotation ann = (Annotation) aFs;
+ return OffsetConversionMode.getConverter(aCtxt, ann.getSofa().getSofaID()) //
+ .map(conv -> conv.mapInternal(aValue)) //
+ .orElse(aValue);
+ }
+
+ return aValue;
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructureSerializer_ImplBase.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructureSerializer_ImplBase.java
new file mode 100644
index 0000000..e206861
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructureSerializer_ImplBase.java
@@ -0,0 +1,94 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static java.util.Arrays.sort;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.ID_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.TYPE_FIELD;
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.json.jsoncas2.JsonCas2Names;
+import org.apache.uima.json.jsoncas2.mode.FeatureStructuresMode;
+import org.apache.uima.json.jsoncas2.mode.ViewsMode;
+import org.apache.uima.json.jsoncas2.ref.FeatureStructureToViewIndex;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public abstract class FeatureStructureSerializer_ImplBase<T extends FeatureStructure>
+ extends StdSerializer<T> {
+ private static final long serialVersionUID = -7548009399965871335L;
+
+ public FeatureStructureSerializer_ImplBase(Class<T> aClazz) {
+ super(aClazz);
+ }
+
+ @Override
+ public void serialize(T aFs, JsonGenerator jg, SerializerProvider aProvider) throws IOException {
+ ReferenceCache refCache = ReferenceCache.get(aProvider);
+ FeatureStructureToViewIndex fsToViewIndex = FeatureStructureToViewIndex.get(aProvider);
+ ViewsMode viewsMode = ViewsMode.get(aProvider);
+ FeatureStructuresMode featureStructuresMode = FeatureStructuresMode.get(aProvider);
+
+ jg.writeStartObject();
+ if (featureStructuresMode == FeatureStructuresMode.AS_ARRAY) {
+ jg.writeNumberField(ID_FIELD, refCache.fsRef(aFs));
+ }
+ jg.writeStringField(TYPE_FIELD, refCache.typeRef(aFs.getType()));
+
+ if (viewsMode == ViewsMode.INLINE) {
+ Set<String> views = fsToViewIndex.getViewsContainingFs(aFs);
+
+ if (views != null && !views.isEmpty()) {
+ String[] viewsArray = views.toArray(new String[views.size()]);
+ sort(viewsArray);
+ jg.writeArrayFieldStart(JsonCas2Names.VIEWS_FIELD);
+ for (String view : viewsArray) {
+ jg.writeString(view);
+ }
+ jg.writeEndArray();
+ }
+ }
+
+ // List<String> flags = new ArrayList<>();
+ // if (((CASImpl) aFs.getCAS()).getDocumentAnnotationNoCreate() == aFs) {
+ // flags.add(FLAG_DOCUMENT_ANNOTATION);
+ // }
+ //
+ // if (!flags.isEmpty()) {
+ // jg.writeArrayFieldStart(JsonCas2Names.FLAGS_FIELD);
+ // for (String flag : flags) {
+ // jg.writeString(flag);
+ // }
+ // jg.writeEndArray();
+ // }
+
+ writeBody(aProvider, jg, aFs);
+
+ jg.writeEndObject();
+ }
+
+ protected abstract void writeBody(SerializerProvider aProvider, JsonGenerator jg,
+ FeatureStructure aFs) throws IOException;
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsArrayDeserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsArrayDeserializer.java
new file mode 100644
index 0000000..001244b
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsArrayDeserializer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.jsoncas2.ser;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.json.jsoncas2.model.FeatureStructures;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+
+public class FeatureStructuresAsArrayDeserializer
+ extends CasDeserializer_ImplBase<FeatureStructures> {
+ private static final long serialVersionUID = -5937326876753347248L;
+
+ public FeatureStructuresAsArrayDeserializer() {
+ super(FeatureStructures.class);
+ }
+
+ @Override
+ public FeatureStructures deserialize(JsonParser aParser, DeserializationContext aCtxt)
+ throws IOException, JsonProcessingException {
+
+ // Consume array begin
+ aParser.nextToken();
+
+ List<FeatureStructure> featureStructures = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ featureStructures.add(aCtxt.readValue(aParser, FeatureStructure.class));
+ aParser.nextToken();
+ }
+
+ runPostprocessors(aCtxt);
+
+ // Consume array end
+ aParser.nextValue();
+
+ return new FeatureStructures(featureStructures);
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsArraySerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsArraySerializer.java
new file mode 100644
index 0000000..43d94b0
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsArraySerializer.java
@@ -0,0 +1,53 @@
+/*
+ * 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.jsoncas2.ser;
+
+import java.io.IOException;
+
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.json.jsoncas2.model.FeatureStructures;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class FeatureStructuresAsArraySerializer extends StdSerializer<FeatureStructures> {
+ private static final long serialVersionUID = 4848917731920209133L;
+
+ public FeatureStructuresAsArraySerializer() {
+ super(FeatureStructures.class);
+ }
+
+ @Override
+ public void serialize(FeatureStructures aFeatureStructures, JsonGenerator jg,
+ SerializerProvider aProvider) throws IOException {
+ if (aFeatureStructures.isEmpty()) {
+ jg.writeStartArray();
+ jg.writeEndArray();
+ return;
+ }
+
+ jg.writeStartArray();
+
+ for (FeatureStructure fs : aFeatureStructures) {
+ aProvider.defaultSerializeValue(fs, jg);
+ }
+ jg.writeEndArray();
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsObjectDeserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsObjectDeserializer.java
new file mode 100644
index 0000000..977bdb8
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsObjectDeserializer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.jsoncas2.ser;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.json.jsoncas2.model.FeatureStructures;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+
+public class FeatureStructuresAsObjectDeserializer
+ extends CasDeserializer_ImplBase<FeatureStructures> {
+ private static final long serialVersionUID = -5937326876753347248L;
+
+ public FeatureStructuresAsObjectDeserializer() {
+ super(FeatureStructures.class);
+ }
+
+ @Override
+ public FeatureStructures deserialize(JsonParser aParser, DeserializationContext aCtxt)
+ throws IOException, JsonProcessingException {
+
+ // Consume object begin
+ aParser.nextValue();
+
+ List<FeatureStructure> featureStructures = new ArrayList<>();
+ while (aParser.currentToken() != JsonToken.END_OBJECT) {
+ featureStructures.add(aCtxt.readValue(aParser, FeatureStructure.class));
+ aParser.nextValue();
+ }
+
+ runPostprocessors(aCtxt);
+
+ // Consume object end
+ aParser.nextValue();
+
+ return new FeatureStructures(featureStructures);
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsObjectSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsObjectSerializer.java
new file mode 100644
index 0000000..6dce30e
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/FeatureStructuresAsObjectSerializer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.jsoncas2.ser;
+
+import java.io.IOException;
+
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.json.jsoncas2.model.FeatureStructures;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class FeatureStructuresAsObjectSerializer extends StdSerializer<FeatureStructures> {
+ private static final long serialVersionUID = 4848917731920209133L;
+
+ public FeatureStructuresAsObjectSerializer() {
+ super(FeatureStructures.class);
+ }
+
+ @Override
+ public void serialize(FeatureStructures aFeatureStructures, JsonGenerator jg,
+ SerializerProvider aProvider) throws IOException {
+ ReferenceCache refCache = ReferenceCache.get(aProvider);
+
+ jg.writeStartObject();
+
+ for (FeatureStructure fs : aFeatureStructures) {
+ jg.writeFieldName(Integer.toString(refCache.fsRef(fs)));
+ aProvider.defaultSerializeValue(fs, jg);
+ }
+
+ jg.writeEndObject();
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/SofaSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/SofaSerializer.java
new file mode 100644
index 0000000..92e72b8
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/SofaSerializer.java
@@ -0,0 +1,56 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.VIEW_MEMBERS_FIELD;
+
+import java.io.IOException;
+
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.jcas.cas.Sofa;
+import org.apache.uima.jcas.cas.TOP;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+@Deprecated
+public class SofaSerializer extends FeatureStructureSerializer {
+ private static final long serialVersionUID = -5346232657650250679L;
+
+ public SofaSerializer() {
+ super(Sofa.class);
+ }
+
+ @Override
+ protected void writeBody(SerializerProvider aProvider, JsonGenerator jg, FeatureStructure aFs)
+ throws IOException {
+ super.writeBody(aProvider, jg, aFs);
+
+ ReferenceCache refCache = ReferenceCache.get(aProvider);
+ Sofa sofa = (Sofa) aFs;
+
+ jg.writeFieldName(VIEW_MEMBERS_FIELD);
+ jg.writeStartArray();
+ for (TOP fs : sofa.getCAS().getView(sofa.getSofaID()).getIndexedFSs()) {
+ jg.writeNumber(refCache.fsRef(fs));
+ }
+ jg.writeEndArray();
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeDeserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeDeserializer.java
new file mode 100644
index 0000000..77959e6
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeDeserializer.java
@@ -0,0 +1,73 @@
+/*
+ * 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.jsoncas2.ser;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.uima.UIMAFramework;
+import org.apache.uima.json.jsoncas2.JsonCas2Names;
+import org.apache.uima.resource.metadata.TypeDescription;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class TypeDeserializer extends CasDeserializer_ImplBase<TypeDescription> {
+ private static final long serialVersionUID = -3406515095847310810L;
+
+ public TypeDeserializer() {
+ super(TypeDescription.class);
+ }
+
+ @Override
+ public TypeDescription deserialize(JsonParser aParser, DeserializationContext aCtxt)
+ throws IOException, JsonProcessingException {
+ JsonNode node = aParser.readValueAsTree();
+
+ if (!node.isObject()) {
+ throw new JsonParseException(aParser, "Type system declaration must be a JSON object");
+ }
+
+ String typeName = node.get(JsonCas2Names.NAME_FIELD).asText();
+ String parentTypeName = node.get(JsonCas2Names.SUPER_TYPE_FIELD).asText();
+ Optional<String> componentType = Optional.ofNullable(node.get(JsonCas2Names.ELEMENT_TYPE_FIELD))
+ .map(JsonNode::asText);
+
+ List<String> featureNames = new ArrayList<>();
+ node.fieldNames().forEachRemaining(name -> {
+ if (!name.startsWith(JsonCas2Names.RESERVED_FIELD_PREFIX)) {
+ featureNames.add(name);
+ }
+ });
+
+ TypeDescription td = UIMAFramework.getResourceSpecifierFactory().createTypeDescription();
+ td.setName(typeName);
+ td.setSupertypeName(parentTypeName);
+ // td.setDescription("");
+ // td.setAllowedValues(null);
+ // td.setFeatures(null);
+ // td.setSourceUrl(aParser.getTokenLocation().sourceDescription());
+ return td;
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeSerializer.java
new file mode 100644
index 0000000..75553d7
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeSerializer.java
@@ -0,0 +1,71 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.ELEMENT_TYPE_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.SUPER_TYPE_FIELD;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.uima.cas.Feature;
+import org.apache.uima.cas.Type;
+import org.apache.uima.cas.impl.TypeImpl;
+import org.apache.uima.json.jsoncas2.JsonCas2Names;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class TypeSerializer extends StdSerializer<Type> {
+ private static final long serialVersionUID = 8549058399080277660L;
+
+ public TypeSerializer() {
+ super(Type.class);
+ }
+
+ @Override
+ public void serialize(Type aType, JsonGenerator aJg, SerializerProvider aProvider)
+ throws IOException {
+ aJg.writeStartObject(aType);
+
+ aJg.writeStringField(JsonCas2Names.NAME_FIELD, aType.getName());
+
+ Type parent = ((TypeImpl) aType).getSuperType();
+ if (parent != null) {
+ aJg.writeStringField(SUPER_TYPE_FIELD, parent.getName());
+ }
+
+ if (aType.getComponentType() != null) {
+ aJg.writeStringField(ELEMENT_TYPE_FIELD, aType.getComponentType().getName());
+ }
+
+ List<Feature> newFeatures = aType.getFeatures().stream().filter(f -> f.getDomain() == aType)
+ .collect(toList());
+ if (!newFeatures.isEmpty()) {
+ for (Feature feature : newFeatures) {
+ aJg.writeFieldName(feature.getShortName());
+ aProvider.defaultSerializeValue(feature, aJg);
+ }
+ }
+
+ aJg.writeEndObject();
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeSystemDeserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeSystemDeserializer.java
new file mode 100644
index 0000000..c20aa3f
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeSystemDeserializer.java
@@ -0,0 +1,64 @@
+/*
+ * 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.jsoncas2.ser;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.uima.UIMAFramework;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.resource.metadata.TypeSystemDescription;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class TypeSystemDeserializer extends CasDeserializer_ImplBase<TypeSystemDescription> {
+ private static final long serialVersionUID = 7137336340824618031L;
+
+ public TypeSystemDeserializer() {
+ super(TypeSystemDescription.class);
+ }
+
+ @Override
+ public TypeSystemDescription deserialize(JsonParser aParser, DeserializationContext aCtxt)
+ throws IOException, JsonProcessingException {
+ JsonNode node = aParser.readValueAsTree();
+
+ if (!node.isObject()) {
+ throw new JsonParseException(aParser, "Type system declaration must be a JSON object");
+ }
+
+ CAS cas = getCas(aCtxt);
+
+ TypeSystemDescription tsd = UIMAFramework.getResourceSpecifierFactory()
+ .createTypeSystemDescription();
+
+ Iterator<String> typeNameIterator = node.fieldNames();
+ while (typeNameIterator.hasNext()) {
+ String typeName = typeNameIterator.next();
+ node.get(typeName);
+ // FIXME !!!
+ }
+
+ return tsd;
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeSystemSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeSystemSerializer.java
new file mode 100644
index 0000000..7be06ca
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/TypeSystemSerializer.java
@@ -0,0 +1,82 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableSet;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.StreamSupport;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Type;
+import org.apache.uima.cas.TypeSystem;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class TypeSystemSerializer extends StdSerializer<TypeSystem> {
+ private static final long serialVersionUID = -4369127219437592227L;
+
+ private final Set<String> BUILT_IN_TYPES = unmodifiableSet(new HashSet<>(asList(
+ CAS.TYPE_NAME_ANNOTATION, CAS.TYPE_NAME_ANNOTATION_BASE, CAS.TYPE_NAME_ARRAY_BASE,
+ CAS.TYPE_NAME_BOOLEAN, CAS.TYPE_NAME_BOOLEAN_ARRAY, CAS.TYPE_NAME_BYTE,
+ CAS.TYPE_NAME_BYTE_ARRAY, CAS.TYPE_NAME_DOCUMENT_ANNOTATION, CAS.TYPE_NAME_DOUBLE,
+ CAS.TYPE_NAME_DOUBLE_ARRAY, CAS.TYPE_NAME_EMPTY_FLOAT_LIST, CAS.TYPE_NAME_EMPTY_FS_LIST,
+ CAS.TYPE_NAME_EMPTY_INTEGER_LIST, CAS.TYPE_NAME_EMPTY_STRING_LIST, CAS.TYPE_NAME_FLOAT,
+ CAS.TYPE_NAME_FLOAT_ARRAY, CAS.TYPE_NAME_FLOAT_LIST, CAS.TYPE_NAME_FS_ARRAY,
+ CAS.TYPE_NAME_FS_LIST, CAS.TYPE_NAME_INTEGER, CAS.TYPE_NAME_INTEGER_ARRAY,
+ CAS.TYPE_NAME_INTEGER_LIST, CAS.TYPE_NAME_LIST_BASE, CAS.TYPE_NAME_LONG,
+ CAS.TYPE_NAME_LONG_ARRAY, CAS.TYPE_NAME_NON_EMPTY_FLOAT_LIST,
+ CAS.TYPE_NAME_NON_EMPTY_FS_LIST, CAS.TYPE_NAME_NON_EMPTY_INTEGER_LIST,
+ CAS.TYPE_NAME_NON_EMPTY_STRING_LIST, CAS.TYPE_NAME_SHORT, CAS.TYPE_NAME_SHORT_ARRAY,
+ CAS.TYPE_NAME_SOFA, CAS.TYPE_NAME_STRING, CAS.TYPE_NAME_STRING_ARRAY,
+ CAS.TYPE_NAME_STRING_LIST, CAS.TYPE_NAME_TOP)));
+
+ public TypeSystemSerializer() {
+ super(TypeSystem.class);
+ }
+
+ @Override
+ public void serialize(TypeSystem aTypeSystem, JsonGenerator jg, SerializerProvider aProvider)
+ throws IOException {
+ ReferenceCache refCache = ReferenceCache.get(aProvider);
+
+ jg.writeStartObject(aTypeSystem);
+
+ List<Type> types = StreamSupport.stream(aTypeSystem.spliterator(), false)
+ .sorted(comparing(Type::getName))
+ .filter(type -> !BUILT_IN_TYPES.contains(type.getName())).collect(toList());
+
+ for (Type type : types) {
+ jg.writeFieldName(refCache.typeRef(type));
+
+ aProvider.defaultSerializeValue(type, jg);
+ }
+
+ jg.writeEndObject();
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/ViewsDeserializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/ViewsDeserializer.java
new file mode 100644
index 0000000..8b6c0e8
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/ViewsDeserializer.java
@@ -0,0 +1,85 @@
+/*
+ * 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.jsoncas2.ser;
+
+import java.io.IOException;
+
+import org.apache.uima.json.jsoncas2.JsonCas2Names;
+import org.apache.uima.json.jsoncas2.model.Views;
+import org.apache.uima.json.jsoncas2.ref.FeatureStructureIdToViewIndex;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+
+public class ViewsDeserializer extends CasDeserializer_ImplBase<Views> {
+ private static final long serialVersionUID = -2976455559005753544L;
+
+ public ViewsDeserializer() {
+ super(Views.class);
+ }
+
+ @Override
+ public Views deserialize(JsonParser aParser, DeserializationContext aCtxt)
+ throws IOException, JsonProcessingException {
+ if (aParser.currentToken() != JsonToken.START_OBJECT) {
+ throw new JsonParseException(aParser, "Views declaration must be a JSON object");
+ }
+
+ // Move to the first view if there is one
+ aParser.nextValue();
+ while (aParser.currentToken() != JsonToken.END_OBJECT) {
+ String viewName = aParser.getCurrentName();
+ deserializeView(aParser, aCtxt, viewName);
+ aParser.nextValue();
+ }
+
+ return new Views(getCas(aCtxt));
+ }
+
+ private void deserializeView(JsonParser aParser, DeserializationContext aCtxt, String aViewName)
+ throws IOException {
+ while (aParser.currentToken() != JsonToken.END_OBJECT) {
+ aParser.nextValue();
+ String fieldName = aParser.getCurrentName();
+
+ switch (fieldName) {
+ case JsonCas2Names.VIEW_SOFA_FIELD:
+ // Ignore
+ break;
+ case JsonCas2Names.VIEW_MEMBERS_FIELD:
+ deserializeIndex(aParser, aCtxt, aViewName);
+ break;
+ }
+ }
+ }
+
+ private void deserializeIndex(JsonParser aParser, DeserializationContext aCtxt, String aViewName)
+ throws IOException {
+ FeatureStructureIdToViewIndex fsIdToViewIdx = FeatureStructureIdToViewIndex.get(aCtxt);
+ aParser.nextToken();
+ while (aParser.currentToken() != JsonToken.END_ARRAY) {
+ fsIdToViewIdx.assignFsToView(aParser.getIntValue(), aViewName);
+ aParser.nextValue();
+ }
+ aParser.nextToken();
+ }
+}
diff --git a/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/ViewsSerializer.java b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/ViewsSerializer.java
new file mode 100644
index 0000000..090c8f3
--- /dev/null
+++ b/uimaj-json/src/main/java/org/apache/uima/json/jsoncas2/ser/ViewsSerializer.java
@@ -0,0 +1,77 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.VIEW_MEMBERS_FIELD;
+import static org.apache.uima.json.jsoncas2.JsonCas2Names.VIEW_SOFA_FIELD;
+
+import java.io.IOException;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.json.jsoncas2.mode.SofaMode;
+import org.apache.uima.json.jsoncas2.model.Views;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+public class ViewsSerializer extends StdSerializer<Views> {
+ private static final long serialVersionUID = 7530813663936058935L;
+
+ public ViewsSerializer() {
+ super(Views.class);
+ }
+
+ @Override
+ public void serialize(Views aViews, JsonGenerator jg, SerializerProvider aProvider)
+ throws IOException {
+ ReferenceCache refCache = ReferenceCache.get(aProvider);
+ SofaMode sofaMode = SofaMode.get(aProvider);
+
+ jg.writeStartObject();
+
+ for (CAS view : aViews) {
+ jg.writeFieldName(view.getViewName());
+
+ jg.writeStartObject();
+
+ switch (sofaMode) {
+ case AS_PART_OF_VIEW:
+ jg.writeFieldName(VIEW_SOFA_FIELD);
+ aProvider.defaultSerializeValue(view.getSofa(), jg);
+ break;
+ case AS_REGULAR_FEATURE_STRUCTURE:
+ jg.writeNumberField(VIEW_SOFA_FIELD, refCache.fsRef(view.getSofa()));
+ break;
+ }
+
+ jg.writeFieldName(VIEW_MEMBERS_FIELD);
+ jg.writeStartArray();
+ for (int fsId : view.getIndexedFSs().stream().mapToInt(refCache::fsRef).sorted().toArray()) {
+ jg.writeNumber(fsId);
+ }
+ jg.writeEndArray();
+
+ jg.writeEndObject();
+ }
+
+ jg.writeEndObject();
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/CasSerializationDeserialization_Json_Test.java b/uimaj-json/src/test/java/org/apache/uima/json/CasSerializationDeserialization_Json_Test.java
new file mode 100644
index 0000000..71b1979
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/CasSerializationDeserialization_Json_Test.java
@@ -0,0 +1,115 @@
+/*
+ * 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 static java.util.stream.Collectors.toList;
+import static org.apache.uima.cas.serdes.TestType.ONE_WAY;
+import static org.apache.uima.cas.serdes.TestType.SER_REF;
+import static org.apache.uima.cas.serdes.datasuites.XmiFileDataSuite.XMI_SUITE_BASE_PATH;
+import static org.apache.uima.json.JsonCasSerializer.jsonSerialize;
+
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.uima.cas.serdes.datasuites.ProgrammaticallyCreatedCasDataSuite;
+import org.apache.uima.cas.serdes.datasuites.XmiFileDataSuite;
+import org.apache.uima.cas.serdes.scenario.SerRefTestScenario;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class CasSerializationDeserialization_Json_Test {
+
+ private static final String CAS_FILE_NAME = "data.json";
+ // private static final int RANDOM_CAS_ITERATIONS = 20;
+ //
+ // private static final List<CasSerDesCycleConfiguration> serDesCycles = asList( //
+ // new CasSerDesCycleConfiguration(FORMAT + " / DEFAULT", //
+ // (a, b) -> serdes(a, b, FORMAT, DEFAULT)),
+ // new CasSerDesCycleConfiguration(FORMAT + " / LENIENT", //
+ // (a, b) -> serdes(a, b, FORMAT, LENIENT)));
+ //
+ // private static final List<CasDesSerCycleConfiguration> desSerCycles = asList( //
+ // new CasDesSerCycleConfiguration(FORMAT + " / DEFAULT", //
+ // (a, b) -> desser(createCas(), a, b, FORMAT, DEFAULT)),
+ // new CasDesSerCycleConfiguration(FORMAT + " / LENIENT", //
+ // (a, b) -> desser(createCas(), a, b, FORMAT, LENIENT)));
+
+ private static List<SerRefTestScenario> serRefScenarios() {
+ Class<?> caller = CasSerializationDeserialization_Json_Test.class;
+ return ProgrammaticallyCreatedCasDataSuite.builder().build().stream()
+ .map(conf -> SerRefTestScenario.builder(caller, conf, SER_REF, CAS_FILE_NAME)
+ .withSerializer((cas, path) -> jsonSerialize(cas, null, path.toFile(), true,
+ null, null)) //
+ .build())
+ .collect(toList());
+ }
+
+ private static List<SerRefTestScenario> oneWayDesSerScenarios() throws Exception {
+ Class<?> caller = CasSerializationDeserialization_Json_Test.class;
+ return XmiFileDataSuite
+ .configurations(Paths.get("..", "uimaj-core").resolve(XMI_SUITE_BASE_PATH)).stream()
+ .map(conf -> SerRefTestScenario.builder(caller, conf, ONE_WAY, CAS_FILE_NAME)
+ .withSerializer((cas, path) -> jsonSerialize(cas, null, path.toFile(), true,
+ null, null)) //
+ .build())
+ .collect(toList());
+ }
+
+ // private static List<DesSerTestScenario> roundTripDesSerScenarios() throws Exception {
+ // return SerDesCasIOTestUtils.roundTripDesSerScenarios(desSerCycles, CAS_FILE_NAME);
+ // }
+ //
+ // private static List<SerDesTestScenario> serDesScenarios() {
+ // return SerDesCasIOTestUtils.serDesScenarios(serDesCycles);
+ // }
+ //
+ // private static List<SerDesTestScenario> randomSerDesScenarios() {
+ // return SerDesCasIOTestUtils.randomSerDesScenarios(serDesCycles, RANDOM_CAS_ITERATIONS);
+ // }
+
+ @ParameterizedTest
+ @MethodSource("serRefScenarios")
+ public void serializeAndCompareToReferenceTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ @ParameterizedTest
+ @MethodSource("oneWayDesSerScenarios")
+ public void oneWayDeserializeSerializeTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ // @ParameterizedTest
+ // @MethodSource("serDesScenarios")
+ // public void serializeDeserializeTest(Runnable aScenario) throws Exception {
+ // aScenario.run();
+ // }
+ //
+ // @ParameterizedTest
+ // @MethodSource("randomSerDesScenarios")
+ // public void randomizedSerializeDeserializeTest(Runnable aScenario) throws Exception {
+ // aScenario.run();
+ // }
+ //
+ // @ParameterizedTest
+ // @MethodSource("roundTripDesSerScenarios")
+ // public void roundTripDeserializeSerializeTest(Runnable aScenario) throws Exception {
+ // aScenario.run();
+ // }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/flexjson/FlexJsonCasDeserializeSerializeTest.java b/uimaj-json/src/test/java/org/apache/uima/json/flexjson/FlexJsonCasDeserializeSerializeTest.java
new file mode 100644
index 0000000..7b1f4e5
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/flexjson/FlexJsonCasDeserializeSerializeTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.flexjson;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Arrays.asList;
+import static org.apache.uima.util.CasCreationUtils.createCas;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.contentOf;
+
+import java.io.File;
+
+import org.apache.uima.cas.CAS;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@Deprecated
+@Ignore("The deserializer is not really implemented...")
+@RunWith(value = Parameterized.class)
+public class FlexJsonCasDeserializeSerializeTest {
+ @Parameters(name = "{index}: running on file {0}")
+ public static Iterable<File> tsvFiles() {
+ return asList(new File("src/test/resources/FlexJsonSerializerTest/")
+ .listFiles(file -> file.isDirectory()));
+ }
+
+ private CAS cas;
+ private File referenceFolder;
+ private File referenceFile;
+ private File outputFile;
+ private JsonFactory jsonFactory;
+
+ public FlexJsonCasDeserializeSerializeTest(File aFolder) throws Exception {
+ referenceFolder = aFolder;
+ referenceFile = new File(referenceFolder, "reference.json");
+ outputFile = new File("target/test-output/" + getClass().getSimpleName() + "/"
+ + referenceFolder.getName() + "/output.json");
+ outputFile.getParentFile().mkdirs();
+
+ cas = createCas();
+
+ jsonFactory = new JsonFactory();
+ jsonFactory.setCodec(new ObjectMapper());
+ }
+
+ @Test
+ public void testReadWrite() throws Exception {
+ FlexJsonCasDeserializer deser = new FlexJsonCasDeserializer(
+ jsonFactory.createParser(referenceFile));
+ deser.read(cas);
+
+ FlexJsonCasSerializer.builder().write(cas, outputFile);
+
+ assertThat(contentOf(outputFile, UTF_8)).isEqualTo(contentOf(referenceFile, UTF_8));
+ // assertEquals(contentOf(referenceFile, UTF_8), contentOf(outputFile, UTF_8), STRICT);
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/flexjson/FlexJsonDeserializerTest.java b/uimaj-json/src/test/java/org/apache/uima/json/flexjson/FlexJsonDeserializerTest.java
new file mode 100644
index 0000000..532bcf4
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/flexjson/FlexJsonDeserializerTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.flexjson;
+
+import static org.apache.uima.util.CasCreationUtils.createCas;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+
+import org.apache.uima.cas.CAS;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@Deprecated
+public class FlexJsonDeserializerTest {
+ private static CAS cas;
+
+ private JsonFactory jsonFactory;
+
+ @BeforeClass
+ public static void setupOnce() throws Exception {
+ cas = createCas();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ jsonFactory = new JsonFactory();
+ jsonFactory.setCodec(new ObjectMapper());
+ }
+
+ @Test
+ public void thatQuotedStringCanBeParsed() throws Exception {
+ FlexJsonCasDeserializer deser = new FlexJsonCasDeserializer(jsonFactory
+ .createParser(new File("src/test/resources/FlexJsonDeserializer/text_only.json")));
+
+ deser.read(cas);
+
+ assertThat(cas.getDocumentText()).isEqualTo("Hello world.");
+ }
+
+ @Test
+ public void thatFeatureStructureArrayCanBeParsed() throws Exception {
+ FlexJsonCasDeserializer deser = new FlexJsonCasDeserializer(jsonFactory.createParser(
+ new File("src/test/resources/FlexJsonDeserializer/feature_structures_only.json")));
+
+ deser.read(cas);
+
+ assertThat(cas.getDocumentText()).isEqualTo("Hello world.");
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/flexjson/FlexJsonSerializerTest.java b/uimaj-json/src/test/java/org/apache/uima/json/flexjson/FlexJsonSerializerTest.java
new file mode 100644
index 0000000..ff2ee6c
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/flexjson/FlexJsonSerializerTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.flexjson;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.uima.UIMAFramework.getResourceSpecifierFactory;
+import static org.apache.uima.cas.CAS.TYPE_NAME_ANNOTATION;
+import static org.apache.uima.json.flexjson.FlexJsonCasSerializer.ViewsMode.INLINE;
+import static org.apache.uima.json.flexjson.FlexJsonCasSerializer.ViewsMode.SEPARATE;
+import static org.apache.uima.util.CasCreationUtils.createCas;
+import static org.assertj.core.api.Assertions.contentOf;
+import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
+import static org.skyscreamer.jsonassert.JSONCompareMode.STRICT;
+
+import java.io.File;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.FeatureStructure;
+import org.apache.uima.cas.Type;
+import org.apache.uima.cas.text.AnnotationFS;
+import org.apache.uima.resource.metadata.TypeDescription;
+import org.apache.uima.resource.metadata.TypeSystemDescription;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestName;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class FlexJsonSerializerTest {
+ public @Rule TemporaryFolder temp = new TemporaryFolder();
+ public @Rule TestName name = new TestName();
+
+ private JsonFactory jsonFactory;
+
+ private File outputFile;
+ private File referenceFile;
+
+ @Before
+ public void setup() throws Exception {
+ jsonFactory = new JsonFactory();
+ jsonFactory.setCodec(new ObjectMapper());
+
+ outputFile = new File("target/test-output/" + getClass().getSimpleName() + "/"
+ + name.getMethodName() + "/output.json");
+ outputFile.getParentFile().mkdirs();
+
+ referenceFile = new File("src/test/resources/" + getClass().getSimpleName() + "/"
+ + name.getMethodName() + "/reference.json");
+ }
+
+ @Test
+ public void multipleViewsAndSofas() throws Exception {
+ CAS cas = createCas();
+ CAS firstView = cas;
+ firstView.setDocumentText("First view");
+ CAS secondView = firstView.createView("secondView");
+ secondView.setDocumentText("Second view");
+
+ FlexJsonCasSerializer.write(cas, outputFile);
+
+ assertEquals(contentOf(referenceFile, UTF_8), contentOf(outputFile, UTF_8), STRICT);
+ }
+
+ @Test
+ public void featureStructureIndexedInMultipleViewsInline() throws Exception {
+ CAS cas = createCas();
+ FeatureStructure fs = cas.createFS(cas.getTypeSystem().getTopType());
+
+ CAS firstView = cas;
+ firstView.setDocumentText("First view");
+ firstView.addFsToIndexes(fs);
+
+ CAS secondView = cas.createView("secondView");
+ secondView.setDocumentText("Second view");
+ secondView.addFsToIndexes(fs);
+
+ FlexJsonCasSerializer.builder() //
+ .setViewsMode(INLINE) //
+ .write(cas, outputFile);
+
+ assertEquals(contentOf(referenceFile, UTF_8), contentOf(outputFile, UTF_8), STRICT);
+ }
+
+ @Test
+ public void featureStructureIndexedInMultipleViewsSeparate() throws Exception {
+ CAS cas = createCas();
+ FeatureStructure fs = cas.createFS(cas.getTypeSystem().getTopType());
+
+ CAS firstView = cas;
+ firstView.setDocumentText("First view");
+ firstView.addFsToIndexes(fs);
+
+ CAS secondView = cas.createView("secondView");
+ secondView.setDocumentText("Second view");
+ secondView.addFsToIndexes(fs);
+
+ FlexJsonCasSerializer.builder() //
+ .setViewsMode(SEPARATE) //
+ .write(cas, outputFile);
+
+ assertEquals(contentOf(referenceFile, UTF_8), contentOf(outputFile, UTF_8), STRICT);
+ }
+
+ @Test
+ public void customAnnotationType() throws Exception {
+ String customTypeName = "custom.Annotation";
+
+ TypeSystemDescription tsd = getResourceSpecifierFactory().createTypeSystemDescription();
+ TypeDescription customTypeDesc = tsd.addType(customTypeName, "", TYPE_NAME_ANNOTATION);
+ customTypeDesc.addFeature("value", "", CAS.TYPE_NAME_STRING);
+
+ CAS cas = createCas(tsd, null, null, null);
+
+ Type customType = cas.getTypeSystem().getType(customTypeName);
+ AnnotationFS fs = cas.createAnnotation(customType, 0, 10);
+ cas.addFsToIndexes(fs);
+
+ FlexJsonCasSerializer.write(cas, outputFile);
+
+ assertEquals(contentOf(referenceFile, UTF_8), contentOf(outputFile, UTF_8), STRICT);
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/CasSerializationDeserialization_JsonCas2_FsAsArray_Test.java b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/CasSerializationDeserialization_JsonCas2_FsAsArray_Test.java
new file mode 100644
index 0000000..74c53f8
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/CasSerializationDeserialization_JsonCas2_FsAsArray_Test.java
@@ -0,0 +1,183 @@
+/*
+ * 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.jsoncas2;
+
+import static java.lang.invoke.MethodHandles.lookup;
+import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.toList;
+import static org.apache.uima.cas.serdes.SerDesCasIOTestUtils.createCasMaybeWithTypesystem;
+import static org.apache.uima.cas.serdes.TestType.ONE_WAY;
+import static org.apache.uima.cas.serdes.TestType.SER_DES;
+import static org.apache.uima.cas.serdes.TestType.SER_REF;
+import static org.apache.uima.cas.serdes.datasuites.XmiFileDataSuite.XMI_SUITE_BASE_PATH;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.serdes.SerDesCasIOTestUtils;
+import org.apache.uima.cas.serdes.datasuites.MultiFeatureRandomCasDataSuite;
+import org.apache.uima.cas.serdes.datasuites.MultiTypeRandomCasDataSuite;
+import org.apache.uima.cas.serdes.datasuites.ProgrammaticallyCreatedCasDataSuite;
+import org.apache.uima.cas.serdes.datasuites.XmiFileDataSuite;
+import org.apache.uima.cas.serdes.scenario.DesSerTestScenario;
+import org.apache.uima.cas.serdes.scenario.SerDesTestScenario;
+import org.apache.uima.cas.serdes.scenario.SerRefTestScenario;
+import org.apache.uima.cas.serdes.transitions.CasDesSerCycleConfiguration;
+import org.apache.uima.cas.serdes.transitions.CasSerDesCycleConfiguration;
+import org.apache.uima.json.jsoncas2.mode.FeatureStructuresMode;
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+import org.apache.uima.json.jsoncas2.mode.SofaMode;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class CasSerializationDeserialization_JsonCas2_FsAsArray_Test {
+
+ private static final String CAS_FILE_NAME = "data.json";
+ private static final int RANDOM_CAS_ITERATIONS = 20;
+
+ private static final List<CasSerDesCycleConfiguration> serDesCycles = asList( //
+ new CasSerDesCycleConfiguration("DEFAULT (default offsets)", //
+ (a, b) -> serdes(a, b, null)),
+ new CasSerDesCycleConfiguration("DEFAULT (UTF-16 offsets)", //
+ (a, b) -> serdes(a, b, OffsetConversionMode.UTF_16)),
+ new CasSerDesCycleConfiguration("DEFAULT (UTF-8 offsets)", //
+ (a, b) -> serdes(a, b, OffsetConversionMode.UTF_8)),
+ new CasSerDesCycleConfiguration("DEFAULT (UTF-32 offsets)", //
+ (a, b) -> serdes(a, b, OffsetConversionMode.UTF_32)));
+ // new CasSerDesCycleConfiguration(FORMAT + " / LENIENT", //
+ // (a, b) -> serdes(a, b, FORMAT, LENIENT)));
+
+ private static final List<CasDesSerCycleConfiguration> desSerCycles = asList( //
+ new CasDesSerCycleConfiguration("DEFAULT", //
+ (a, b) -> desser(createCasMaybeWithTypesystem(a), a, b)));
+
+ private static void ser(CAS aSourceCas, Path aTargetCasFile) throws IOException {
+ JsonCas2Serializer serializer = new JsonCas2Serializer();
+ serializer.setFsMode(FeatureStructuresMode.AS_ARRAY);
+ serializer.setSofaMode(SofaMode.AS_REGULAR_FEATURE_STRUCTURE);
+ serializer.serialize(aSourceCas, aTargetCasFile.toFile());
+ }
+
+ private static void des(CAS aTargetCas, Path aSourceCasFile) throws IOException {
+ JsonCas2Deserializer deserializer = new JsonCas2Deserializer();
+ deserializer.setFsMode(FeatureStructuresMode.AS_ARRAY);
+ deserializer.deserialize(aSourceCasFile.toFile(), aTargetCas);
+ }
+
+ public static void serdes(CAS aSourceCas, CAS aTargetCas, OffsetConversionMode aOcm)
+ throws Exception {
+ byte[] buffer;
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ JsonCas2Serializer serializer = new JsonCas2Serializer();
+ serializer.setFsMode(FeatureStructuresMode.AS_ARRAY);
+ serializer.setSofaMode(SofaMode.AS_REGULAR_FEATURE_STRUCTURE);
+ serializer.setOffsetConversionMode(aOcm);
+ serializer.serialize(aSourceCas, os);
+ buffer = os.toByteArray();
+ }
+
+ Path targetFile = SER_DES.getTargetFolder(lookup().lookupClass()).resolve(CAS_FILE_NAME);
+ Files.createDirectories(targetFile.getParent());
+ try (OutputStream os = Files.newOutputStream(targetFile)) {
+ os.write(buffer);
+ }
+
+ try (InputStream is = new ByteArrayInputStream(buffer)) {
+ JsonCas2Deserializer deserializer = new JsonCas2Deserializer();
+ deserializer.setFsMode(FeatureStructuresMode.AS_ARRAY);
+ deserializer.deserialize(is, aTargetCas);
+ }
+ }
+
+ public static void desser(CAS aBufferCas, Path aSourceCasPath, Path aTargetCasPath)
+ throws Exception {
+ des(aBufferCas, aSourceCasPath);
+ ser(aBufferCas, aTargetCasPath);
+ }
+
+ private static List<SerRefTestScenario> serRefScenarios() {
+ Class<?> caller = CasSerializationDeserialization_JsonCas2_FsAsArray_Test.class;
+ return ProgrammaticallyCreatedCasDataSuite.builder().build().stream()
+ .map(conf -> SerRefTestScenario.builder(caller, conf, SER_REF, CAS_FILE_NAME)
+ .withSerializer((cas, path) -> ser(cas, path)).build())
+ .collect(toList());
+ }
+
+ private static List<SerRefTestScenario> oneWayDesSerScenarios() throws Exception {
+ Class<?> caller = CasSerializationDeserialization_JsonCas2_FsAsArray_Test.class;
+ return XmiFileDataSuite
+ .configurations(Paths.get("..", "uimaj-core").resolve(XMI_SUITE_BASE_PATH)).stream()
+ .map(conf -> SerRefTestScenario.builder(caller, conf, ONE_WAY, CAS_FILE_NAME)
+ .withSerializer((cas, path) -> ser(cas, path)).build())
+ .collect(toList());
+ }
+
+ private static List<DesSerTestScenario> roundTripDesSerScenarios() throws Exception {
+ return SerDesCasIOTestUtils.roundTripDesSerScenariosComparingFileContents(desSerCycles,
+ CAS_FILE_NAME);
+ }
+
+ private static List<SerDesTestScenario> serDesScenarios() {
+ return SerDesCasIOTestUtils.programmaticSerDesScenarios(serDesCycles);
+ }
+
+ private static List<SerDesTestScenario> randomSerDesScenarios() {
+ return SerDesCasIOTestUtils.serDesScenarios(serDesCycles,
+ MultiFeatureRandomCasDataSuite.builder().withIterations(RANDOM_CAS_ITERATIONS).build(),
+ MultiTypeRandomCasDataSuite.builder().withIterations(RANDOM_CAS_ITERATIONS).build());
+ }
+
+ @ParameterizedTest
+ @MethodSource("serRefScenarios")
+ public void serializeAndCompareToReferenceTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ @ParameterizedTest
+ @MethodSource("oneWayDesSerScenarios")
+ public void oneWayDeserializeSerializeTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ @ParameterizedTest
+ @MethodSource("serDesScenarios")
+ public void serializeDeserializeTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ @ParameterizedTest
+ @MethodSource("randomSerDesScenarios")
+ public void randomizedSerializeDeserializeTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ @ParameterizedTest
+ @MethodSource("roundTripDesSerScenarios")
+ public void roundTripDeserializeSerializeTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/CasSerializationDeserialization_JsonCas2_FsAsObject_Test.java b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/CasSerializationDeserialization_JsonCas2_FsAsObject_Test.java
new file mode 100644
index 0000000..5ad8145
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/CasSerializationDeserialization_JsonCas2_FsAsObject_Test.java
@@ -0,0 +1,183 @@
+/*
+ * 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.jsoncas2;
+
+import static java.lang.invoke.MethodHandles.lookup;
+import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.toList;
+import static org.apache.uima.cas.serdes.SerDesCasIOTestUtils.createCasMaybeWithTypesystem;
+import static org.apache.uima.cas.serdes.TestType.ONE_WAY;
+import static org.apache.uima.cas.serdes.TestType.SER_DES;
+import static org.apache.uima.cas.serdes.TestType.SER_REF;
+import static org.apache.uima.cas.serdes.datasuites.XmiFileDataSuite.XMI_SUITE_BASE_PATH;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.serdes.SerDesCasIOTestUtils;
+import org.apache.uima.cas.serdes.datasuites.MultiFeatureRandomCasDataSuite;
+import org.apache.uima.cas.serdes.datasuites.MultiTypeRandomCasDataSuite;
+import org.apache.uima.cas.serdes.datasuites.ProgrammaticallyCreatedCasDataSuite;
+import org.apache.uima.cas.serdes.datasuites.XmiFileDataSuite;
+import org.apache.uima.cas.serdes.scenario.DesSerTestScenario;
+import org.apache.uima.cas.serdes.scenario.SerDesTestScenario;
+import org.apache.uima.cas.serdes.scenario.SerRefTestScenario;
+import org.apache.uima.cas.serdes.transitions.CasDesSerCycleConfiguration;
+import org.apache.uima.cas.serdes.transitions.CasSerDesCycleConfiguration;
+import org.apache.uima.json.jsoncas2.mode.FeatureStructuresMode;
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+import org.apache.uima.json.jsoncas2.mode.SofaMode;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class CasSerializationDeserialization_JsonCas2_FsAsObject_Test {
+
+ private static final String CAS_FILE_NAME = "data.json";
+ private static final int RANDOM_CAS_ITERATIONS = 20;
+
+ private static final List<CasSerDesCycleConfiguration> serDesCycles = asList( //
+ new CasSerDesCycleConfiguration("DEFAULT (default offsets)", //
+ (a, b) -> serdes(a, b, null)),
+ new CasSerDesCycleConfiguration("DEFAULT (UTF-16 offsets)", //
+ (a, b) -> serdes(a, b, OffsetConversionMode.UTF_16)),
+ new CasSerDesCycleConfiguration("DEFAULT (UTF-8 offsets)", //
+ (a, b) -> serdes(a, b, OffsetConversionMode.UTF_8)),
+ new CasSerDesCycleConfiguration("DEFAULT (UTF-32 offsets)", //
+ (a, b) -> serdes(a, b, OffsetConversionMode.UTF_32)));
+ // new CasSerDesCycleConfiguration(FORMAT + " / LENIENT", //
+ // (a, b) -> serdes(a, b, FORMAT, LENIENT)));
+
+ private static final List<CasDesSerCycleConfiguration> desSerCycles = asList( //
+ new CasDesSerCycleConfiguration("DEFAULT", //
+ (a, b) -> desser(createCasMaybeWithTypesystem(a), a, b)));
+
+ private static void ser(CAS aSourceCas, Path aTargetCasFile) throws IOException {
+ JsonCas2Serializer serializer = new JsonCas2Serializer();
+ serializer.setFsMode(FeatureStructuresMode.AS_OBJECT);
+ serializer.setSofaMode(SofaMode.AS_REGULAR_FEATURE_STRUCTURE);
+ serializer.serialize(aSourceCas, aTargetCasFile.toFile());
+ }
+
+ private static void des(CAS aTargetCas, Path aSourceCasFile) throws IOException {
+ JsonCas2Deserializer deserializer = new JsonCas2Deserializer();
+ deserializer.setFsMode(FeatureStructuresMode.AS_OBJECT);
+ deserializer.deserialize(aSourceCasFile.toFile(), aTargetCas);
+ }
+
+ public static void serdes(CAS aSourceCas, CAS aTargetCas, OffsetConversionMode aOcm)
+ throws IOException {
+ byte[] buffer;
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ JsonCas2Serializer serializer = new JsonCas2Serializer();
+ serializer.setFsMode(FeatureStructuresMode.AS_OBJECT);
+ serializer.setSofaMode(SofaMode.AS_REGULAR_FEATURE_STRUCTURE);
+ serializer.setOffsetConversionMode(aOcm);
+ serializer.serialize(aSourceCas, os);
+ buffer = os.toByteArray();
+ }
+
+ Path targetFile = SER_DES.getTargetFolder(lookup().lookupClass()).resolve(CAS_FILE_NAME);
+ Files.createDirectories(targetFile.getParent());
+ try (OutputStream os = Files.newOutputStream(targetFile)) {
+ os.write(buffer);
+ }
+
+ try (InputStream is = new ByteArrayInputStream(buffer)) {
+ JsonCas2Deserializer deserializer = new JsonCas2Deserializer();
+ deserializer.setFsMode(FeatureStructuresMode.AS_OBJECT);
+ deserializer.deserialize(is, aTargetCas);
+ }
+ }
+
+ public static void desser(CAS aBufferCas, Path aSourceCasPath, Path aTargetCasPath)
+ throws Exception {
+ des(aBufferCas, aSourceCasPath);
+ ser(aBufferCas, aTargetCasPath);
+ }
+
+ private static List<SerRefTestScenario> serRefScenarios() {
+ Class<?> caller = CasSerializationDeserialization_JsonCas2_FsAsObject_Test.class;
+ return ProgrammaticallyCreatedCasDataSuite.builder().build().stream()
+ .map(conf -> SerRefTestScenario.builder(caller, conf, SER_REF, CAS_FILE_NAME)
+ .withSerializer((cas, path) -> ser(cas, path)).build())
+ .collect(toList());
+ }
+
+ private static List<SerRefTestScenario> oneWayDesSerScenarios() throws Exception {
+ Class<?> caller = CasSerializationDeserialization_JsonCas2_FsAsObject_Test.class;
+ return XmiFileDataSuite
+ .configurations(Paths.get("..", "uimaj-core").resolve(XMI_SUITE_BASE_PATH)).stream()
+ .map(conf -> SerRefTestScenario.builder(caller, conf, ONE_WAY, CAS_FILE_NAME)
+ .withSerializer((cas, path) -> ser(cas, path)).build())
+ .collect(toList());
+ }
+
+ private static List<DesSerTestScenario> roundTripDesSerScenarios() throws Exception {
+ return SerDesCasIOTestUtils.roundTripDesSerScenariosComparingFileContents(desSerCycles,
+ CAS_FILE_NAME);
+ }
+
+ private static List<SerDesTestScenario> serDesScenarios() {
+ return SerDesCasIOTestUtils.programmaticSerDesScenarios(serDesCycles);
+ }
+
+ private static List<SerDesTestScenario> randomSerDesScenarios() {
+ return SerDesCasIOTestUtils.serDesScenarios(serDesCycles,
+ MultiFeatureRandomCasDataSuite.builder().withIterations(RANDOM_CAS_ITERATIONS).build(),
+ MultiTypeRandomCasDataSuite.builder().withIterations(RANDOM_CAS_ITERATIONS).build());
+ }
+
+ @ParameterizedTest
+ @MethodSource("serRefScenarios")
+ public void serializeAndCompareToReferenceTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ @ParameterizedTest
+ @MethodSource("oneWayDesSerScenarios")
+ public void oneWayDeserializeSerializeTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ @ParameterizedTest
+ @MethodSource("serDesScenarios")
+ public void serializeDeserializeTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ @ParameterizedTest
+ @MethodSource("randomSerDesScenarios")
+ public void randomizedSerializeDeserializeTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+
+ @ParameterizedTest
+ @MethodSource("roundTripDesSerScenarios")
+ public void roundTripDeserializeSerializeTest(Runnable aScenario) throws Exception {
+ aScenario.run();
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/Performance_JsonCas2_FsAsArray_Test.java b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/Performance_JsonCas2_FsAsArray_Test.java
new file mode 100644
index 0000000..eb92976
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/Performance_JsonCas2_FsAsArray_Test.java
@@ -0,0 +1,78 @@
+/*
+ * 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.jsoncas2;
+
+import java.util.Random;
+
+import org.apache.uima.cas.serdes.PerformanceTestRunner;
+import org.apache.uima.cas.serdes.generators.MultiFeatureRandomCasGenerator;
+import org.apache.uima.json.jsoncas2.mode.FeatureStructuresMode;
+import org.apache.uima.json.jsoncas2.mode.SofaMode;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class Performance_JsonCas2_FsAsArray_Test {
+
+ private static final int ITERATIONS = 100;
+ private static final int SIZE = 1_000;
+
+ private static JsonCas2Serializer jsonSerializer;
+ private static JsonCas2Deserializer jsonDeserializer;
+
+ @BeforeAll
+ public static void setup() throws Exception {
+ jsonSerializer = new JsonCas2Serializer();
+ jsonSerializer.setFsMode(FeatureStructuresMode.AS_ARRAY);
+ jsonSerializer.setSofaMode(SofaMode.AS_REGULAR_FEATURE_STRUCTURE);
+
+ jsonDeserializer = new JsonCas2Deserializer();
+ jsonDeserializer.setFsMode(jsonSerializer.getFsMode());
+ }
+
+ @Test
+ public void jsonSerialization() throws Exception {
+ PerformanceTestRunner runner = buildRunner();
+ long serDuration = runner.measureSerializationPerformance();
+
+ System.out.printf("[%23s] %d CASes with %d feature structures (%7d bytes each)%n", "JSON",
+ ITERATIONS, SIZE, runner.getDataSize());
+
+ System.out.printf("[%23s] %6s ms serialization %6.2f fs/sec %6.2f CAS/sec %n", "JSON",
+ serDuration, (ITERATIONS * SIZE) / (serDuration / 1000.0d),
+ ITERATIONS / (serDuration / 1000.0d));
+
+ long desDuration = runner.measureDeserializationPerformance();
+
+ System.out.printf("[%23s] %6s ms deserialization %6.2f fs/sec %6.2f CAS/sec %n", "JSON",
+ desDuration, (ITERATIONS * SIZE) / (desDuration / 1000.0d),
+ ITERATIONS / (desDuration / 1000.0d));
+ }
+
+ public PerformanceTestRunner buildRunner() throws Exception {
+ return PerformanceTestRunner.builder() //
+ .withIterations(ITERATIONS) //
+ .withDeserializer(jsonDeserializer::deserialize)
+ .withSerializer(jsonSerializer::serialize) //
+ .withGenerator(MultiFeatureRandomCasGenerator.builder() //
+ .withRandomGenerator(new Random(123456l)) //
+ .withSize(SIZE) //
+ .build()) //
+ .build();
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/encoding/Utf32CodepointOffsetConverterTest.java b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/encoding/Utf32CodepointOffsetConverterTest.java
new file mode 100644
index 0000000..171f5d9
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/encoding/Utf32CodepointOffsetConverterTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.jsoncas2.encoding;
+
+import static java.lang.Character.charCount;
+import static java.lang.Character.getName;
+import static java.lang.Character.toChars;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static net.javacrumbs.jsonunit.jsonpath.JsonPathAdapter.inPath;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.ByteArrayOutputStream;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.serdes.datasuites.ProgrammaticallyCreatedCasDataSuite;
+import org.apache.uima.cas.text.AnnotationFS;
+import org.apache.uima.json.jsoncas2.JsonCas2Serializer;
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+import org.junit.jupiter.api.Test;
+
+import net.javacrumbs.jsonunit.assertj.JsonAssertions;
+
+public class Utf32CodepointOffsetConverterTest {
+ @Test
+ public void thatMappingExternalToInternalWorks() {
+ int[] externalToInternal = {
+ // Smily
+ 0,
+ // " This "
+ 2, 3, 4, 5, 6, 7,
+ // Female light-skinned turban emoji
+ 8, 10, 12, 13, 14,
+ // " is "
+ 15, 16, 17, 18,
+ // Telephone sign
+ 19,
+ // " a "
+ 20, 21, 22,
+ // Dark skinned bearded emoji
+ 23, 25, 27, 28, 29,
+ // " test "
+ 30, 31, 32, 33, 34, 35,
+ // Ghost emoji
+ 36,
+ // EOS
+ 38 };
+ String text = "🥳 This 👳🏻♀️ is ✆ a 🧔🏾♂️ test 👻";
+ Utf32CodepointOffsetConverter conv = new Utf32CodepointOffsetConverter(text);
+ for (int n = 0; n < externalToInternal.length; n++) {
+ int mn = externalToInternal[n];
+ if (mn < text.length()) {
+ int cp = text.codePointAt(mn);
+ int w = charCount(cp);
+ System.out.printf("%2d->%2d: U+%05X (%d) %s %s%n", n, mn, cp, w,
+ String.valueOf(toChars(cp)), getName(cp));
+ } else {
+ System.out.printf("%2d->%2d: EOS%n", n, mn);
+ }
+ assertThat(conv.mapExternal(n)).isEqualTo(mn);
+ }
+ }
+
+ @Test
+ public void thatMappingInternalToExternalWorks() {
+ final int U = Utf32CodepointOffsetConverter.UNMAPPED;
+ int[] internalToExternal = {
+ // Smily
+ 0, U,
+ // " This "
+ 1, 2, 3, 4, 5, 6,
+ // Female light-skinned turban emoji
+ 7, U, 8, U, 9, 10, 11,
+ // " is "
+ 12, 13, 14, 15,
+ // Telephone sign
+ 16,
+ // " a "
+ 17, 18, 19,
+ // Dark skinned bearded emoji
+ 20, U, 21, U, 22, 23, 24,
+ // " test "
+ 25, 26, 27, 28, 29, 30,
+ // Ghost emoji
+ 31, U,
+ // EOS
+ 32 };
+ String text = "🥳 This 👳🏻♀️ is ✆ a 🧔🏾♂️ test 👻";
+ int cpl = text.codePointCount(0, text.length());
+ Utf32CodepointOffsetConverter conv = new Utf32CodepointOffsetConverter(text);
+ for (int n = 0; n < internalToExternal.length; n++) {
+ int mn = internalToExternal[n];
+ if (mn < cpl) {
+ if (mn != Utf32CodepointOffsetConverter.UNMAPPED) {
+ int cp = text.codePointAt(n);
+ int w = Character.charCount(cp);
+ // System.out.printf("%2d->%2d: U+%05X (%d) %s %s%n", n, mn, cp, w,
+ // String.valueOf(toChars(cp)), getName(cp));
+ } else {
+ // System.out.printf("%2d->%2d: EOS%n", n, mn);
+ }
+ }
+
+ assertThat(conv.mapInternal(n)).isEqualTo(mn);
+ }
+ }
+
+ @Test
+ public void thatSerializationWithMappingWorks() throws Exception {
+ JsonCas2Serializer ser = new JsonCas2Serializer();
+ ser.setOffsetConversionMode(OffsetConversionMode.UTF_32);
+
+ CAS cas = ProgrammaticallyCreatedCasDataSuite.casWithEmojiUnicodeTextAndAnnotations();
+
+ String casJson;
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ ser.serialize(cas, os);
+ casJson = new String(os.toByteArray(), UTF_8);
+ }
+
+ String expected = String.join(",",
+ "{'%ID':1,'%TYPE':'uima.cas.Sofa','sofaNum':1,'sofaID':'_InitialView','mimeType':'text','sofaString':'🥳 This 👳🏻♀️ is ✆ a 🧔🏾♂️ test 👻'}",
+ "{'%ID':2,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':0,'end':1}",
+ "{'%ID':3,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':2,'end':6}",
+ "{'%ID':4,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':7,'end':12}",
+ "{'%ID':5,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':13,'end':15}",
+ "{'%ID':6,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':16,'end':17}",
+ "{'%ID':7,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':18,'end':19}",
+ "{'%ID':8,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':20,'end':25}",
+ "{'%ID':9,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':26,'end':30}",
+ "{'%ID':10,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':31,'end':32}",
+ "{'%ID':11,'%TYPE':'uima.tcas.DocumentAnnotation','@sofa':1,'begin':0,'end':32,'language':'x-unspecified'}");
+ expected = "[" + expected + "]";
+
+ JsonAssertions.assertThatJson(inPath(casJson, "$.%FEATURE_STRUCTURES[*])")) //
+ .isEqualTo(expected);
+ }
+
+ private static void createAnnotatedText(CAS aCas, StringBuilder aBuffer, String aText,
+ String... aSuffix) {
+ int begin = aBuffer.length();
+ aBuffer.append(aText);
+ AnnotationFS a = aCas.createAnnotation(aCas.getAnnotationType(), begin, aBuffer.length());
+ aCas.addFsToIndexes(a);
+ for (String s : aSuffix) {
+ aBuffer.append(s);
+ }
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/encoding/Utf8ByteOffsetConverterTest.java b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/encoding/Utf8ByteOffsetConverterTest.java
new file mode 100644
index 0000000..8ca966f
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/encoding/Utf8ByteOffsetConverterTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.jsoncas2.encoding;
+
+import static java.lang.Character.getName;
+import static java.lang.Character.toChars;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static net.javacrumbs.jsonunit.jsonpath.JsonPathAdapter.inPath;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.ByteArrayOutputStream;
+
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.serdes.datasuites.ProgrammaticallyCreatedCasDataSuite;
+import org.apache.uima.cas.text.AnnotationFS;
+import org.apache.uima.json.jsoncas2.JsonCas2Serializer;
+import org.apache.uima.json.jsoncas2.mode.OffsetConversionMode;
+import org.junit.jupiter.api.Test;
+
+import net.javacrumbs.jsonunit.assertj.JsonAssertions;
+
+public class Utf8ByteOffsetConverterTest {
+ @Test
+ public void thatMappingExternalToInternalWorks() {
+ final int U = Utf8ByteOffsetConverter.UNMAPPED;
+ int[] externalToInternal = {
+ // Smily
+ 0, U, U, U,
+ // " This "
+ 2, 3, 4, 5, 6, 7,
+ // Female light-skinned turban emoji
+ 8, U, U, U, 10, U, U, U, 12, U, U, 13, U, U, 14, U, U,
+ // " is "
+ 15, 16, 17, 18,
+ // Telephone sign
+ 19, U, U,
+ // " a "
+ 20, 21, 22,
+ // Dark skinned bearded emoji
+ 23, U, U, U, 25, U, U, U, 27, U, U, 28, U, U, 29, U, U,
+ // " test "
+ 30, 31, 32, 33, 34, 35,
+ // Ghost emoji
+ 36, U, U, U,
+ // EOS
+ 38 };
+ String text = "🥳 This 👳🏻♀️ is ✆ a 🧔🏾♂️ test 👻";
+ int external_length = text.getBytes(UTF_8).length;
+ Utf8ByteOffsetConverter conv = new Utf8ByteOffsetConverter(text);
+ for (int n = 0; n < externalToInternal.length; n++) {
+ int mn = externalToInternal[n];
+ if (n < external_length) {
+ if (mn != Utf8ByteOffsetConverter.UNMAPPED) {
+ int cp = text.codePointAt(mn);
+ String s = String.valueOf(toChars(cp));
+ int w = s.getBytes(UTF_8).length;
+ System.out.printf("%2d->%2d: U+%05X (%d) %s %s%n", n, mn, cp, w, s, getName(cp));
+ }
+ } else {
+ System.out.printf("%2d->%2d: EOS%n", n, mn);
+ }
+
+ assertThat(conv.mapExternal(n)).isEqualTo(mn);
+ }
+ }
+
+ @Test
+ public void thatMappingInternalToExternalWorks() {
+ final int U = Utf8ByteOffsetConverter.UNMAPPED;
+ int[] internalToExternal = {
+ // Smily
+ 0, U,
+ // " This "
+ 4, 5, 6, 7, 8, 9,
+ // Female light-skinned turban emoji
+ 10, U, 14, U, 18, 21, 24,
+ // " is "
+ 27, 28, 29, 30,
+ // Telephone sign
+ 31,
+ // " a "
+ 34, 35, 36,
+ // Dark skinned bearded emoji
+ 37, U, 41, U, 45, 48, 51,
+ // " test "
+ 54, 55, 56, 57, 58, 59,
+ // Ghost emoji
+ 60, U,
+ // EOS
+ 64 };
+ String text = "🥳 This 👳🏻♀️ is ✆ a 🧔🏾♂️ test 👻";
+ int cul = text.length();
+ Utf8ByteOffsetConverter conv = new Utf8ByteOffsetConverter(text);
+ for (int n = 0; n < internalToExternal.length; n++) {
+ int mn = internalToExternal[n];
+ if (n < cul) {
+ if (mn != Utf8ByteOffsetConverter.UNMAPPED) {
+ int cp = text.codePointAt(n);
+ String s = String.valueOf(toChars(cp));
+ int w = String.valueOf(Character.toChars(cp)).getBytes(UTF_8).length;
+ // System.out.printf("%2d->%2d: U+%05X (%d) %s %s%n", n, mn, cp, w, s, getName(cp));
+ }
+ } else {
+ // System.out.printf("%2d->%2d: EOS%n", n, mn);
+ }
+
+ assertThat(conv.mapInternal(n)).isEqualTo(mn);
+ }
+ }
+
+ @Test
+ public void thatSerializationWithMappingWorks() throws Exception {
+ JsonCas2Serializer ser = new JsonCas2Serializer();
+ ser.setOffsetConversionMode(OffsetConversionMode.UTF_8);
+
+ CAS cas = ProgrammaticallyCreatedCasDataSuite.casWithEmojiUnicodeTextAndAnnotations();
+
+ String casJson;
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ ser.serialize(cas, os);
+ casJson = new String(os.toByteArray(), UTF_8);
+ }
+
+ String expected = String.join(",",
+ "{'%ID':1,'%TYPE':'uima.cas.Sofa','sofaNum':1,'sofaID':'_InitialView','mimeType':'text','sofaString':'🥳 This 👳🏻♀️ is ✆ a 🧔🏾♂️ test 👻'}",
+ "{'%ID':2,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':0,'end':4}",
+ "{'%ID':3,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':5,'end':9}",
+ "{'%ID':4,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':10,'end':27}",
+ "{'%ID':5,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':28,'end':30}",
+ "{'%ID':6,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':31,'end':34}",
+ "{'%ID':7,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':35,'end':36}",
+ "{'%ID':8,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':37,'end':54}",
+ "{'%ID':9,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':55,'end':59}",
+ "{'%ID':10,'%TYPE':'uima.tcas.Annotation','@sofa':1,'begin':60,'end':64}",
+ "{'%ID':11,'%TYPE':'uima.tcas.DocumentAnnotation','@sofa':1,'begin':0,'end':64,'language':'x-unspecified'}");
+ expected = "[" + expected + "]";
+
+ JsonAssertions.assertThatJson(inPath(casJson, "$.%FEATURE_STRUCTURES[*])")) //
+ .isEqualTo(expected);
+ }
+
+ private static void createAnnotatedText(CAS aCas, StringBuilder aBuffer, String aText,
+ String... aSuffix) {
+ int begin = aBuffer.length();
+ aBuffer.append(aText);
+ AnnotationFS a = aCas.createAnnotation(aCas.getAnnotationType(), begin, aBuffer.length());
+ aCas.addFsToIndexes(a);
+ for (String s : aSuffix) {
+ aBuffer.append(s);
+ }
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/ser/FeatureDeSerializerTest.java b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/ser/FeatureDeSerializerTest.java
new file mode 100644
index 0000000..e144757
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/ser/FeatureDeSerializerTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static org.apache.uima.cas.CAS.TYPE_NAME_ANNOTATION;
+import static org.apache.uima.cas.CAS.TYPE_NAME_FS_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_FS_LIST;
+import static org.apache.uima.cas.CAS.TYPE_NAME_INTEGER;
+import static org.apache.uima.cas.CAS.TYPE_NAME_INTEGER_ARRAY;
+import static org.apache.uima.cas.CAS.TYPE_NAME_STRING;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.StringWriter;
+
+import org.apache.uima.UIMAFramework;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Feature;
+import org.apache.uima.cas.TypeSystem;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+import org.apache.uima.resource.metadata.FeatureDescription;
+import org.apache.uima.resource.metadata.TypeDescription;
+import org.apache.uima.resource.metadata.TypeSystemDescription;
+import org.apache.uima.util.CasCreationUtils;
+import org.apache.uima.util.TypeSystemUtil;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+public class FeatureDeSerializerTest {
+ private Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String TYPE = "Type";
+ private static final String FEATURE = "feature";
+
+ private TypeSystemDescription tsd;
+ private TypeDescription td;
+
+ @BeforeEach
+ public void setup() {
+ tsd = UIMAFramework.getResourceSpecifierFactory().createTypeSystemDescription();
+ td = tsd.addType(TYPE, null, CAS.TYPE_NAME_ANNOTATION);
+ }
+
+ @Test
+ public void testIntegerFeature() throws Exception {
+ FeatureDescription expected = td.addFeature(FEATURE, null, TYPE_NAME_INTEGER);
+ FeatureDescription actual = serdes(tsd);
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testStringFeature() throws Exception {
+ FeatureDescription expected = td.addFeature(FEATURE, null, TYPE_NAME_STRING);
+ FeatureDescription actual = serdes(tsd);
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testFSArrayFeature() throws Exception {
+ FeatureDescription expected = td.addFeature(FEATURE, null, TYPE_NAME_FS_ARRAY,
+ TYPE_NAME_ANNOTATION, null);
+ FeatureDescription actual = serdes(tsd);
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testIntegerArrayFeature() throws Exception {
+ FeatureDescription expected = td.addFeature(FEATURE, null, TYPE_NAME_INTEGER_ARRAY,
+ TYPE_NAME_INTEGER, null);
+ FeatureDescription actual = serdes(tsd);
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
+ public void testFSListFeature() throws Exception {
+ FeatureDescription expected = td.addFeature(FEATURE, null, TYPE_NAME_FS_LIST,
+ TYPE_NAME_ANNOTATION, null);
+ FeatureDescription actual = serdes(tsd);
+
+ // HACK: UIMA does not preserve the element type for FSList features!!! Bug?
+ expected.setElementType(null);
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ private FeatureDescription serdes(TypeSystemDescription aTsd) throws Exception {
+ ObjectMapper mapper = getMapper();
+ CAS cas = CasCreationUtils.createCas(aTsd, null, null, null);
+ TypeSystem ts = cas.getTypeSystem();
+
+ StringWriter buf = new StringWriter();
+ TypeSystemUtil.type2TypeDescription(ts.getType(TYPE), ts).toXML(buf);
+
+ String json = mapper.writer() //
+ .withAttribute(ReferenceCache.KEY, ReferenceCache.builder().build()) //
+ .writeValueAsString(ts.getType(TYPE).getFeatureByBaseName(FEATURE));
+
+ FeatureDescription fdActual = mapper.reader() //
+ .forType(FeatureDescription.class) //
+ .readValue(json);
+
+ return fdActual;
+ }
+
+ private ObjectMapper getMapper() {
+ SimpleModule module = new SimpleModule("UIMA CAS JSON", new Version(1, 0, 0, null, null, null));
+
+ module.addSerializer(Feature.class, new FeatureSerializer());
+ module.addDeserializer(FeatureDescription.class, new FeatureDeserializer());
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(module);
+ return mapper;
+ }
+}
diff --git a/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/ser/TypeDeSerializerTest.java b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/ser/TypeDeSerializerTest.java
new file mode 100644
index 0000000..7e97998
--- /dev/null
+++ b/uimaj-json/src/test/java/org/apache/uima/json/jsoncas2/ser/TypeDeSerializerTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.jsoncas2.ser;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.uima.UIMAFramework;
+import org.apache.uima.cas.CAS;
+import org.apache.uima.cas.Feature;
+import org.apache.uima.cas.Type;
+import org.apache.uima.cas.TypeSystem;
+import org.apache.uima.json.jsoncas2.ref.ReferenceCache;
+import org.apache.uima.resource.metadata.FeatureDescription;
+import org.apache.uima.resource.metadata.TypeDescription;
+import org.apache.uima.resource.metadata.TypeSystemDescription;
+import org.apache.uima.util.CasCreationUtils;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+public class TypeDeSerializerTest {
+ private Logger log = LoggerFactory.getLogger(getClass());
+
+ @Test
+ public void thatTypeDeSerializationWorks() throws Exception {
+ ObjectMapper mapper = getMapper();
+
+ TypeSystemDescription tsd = UIMAFramework.getResourceSpecifierFactory()
+ .createTypeSystemDescription();
+ TypeDescription tdExpected = tsd.addType("Type1", null, CAS.TYPE_NAME_ANNOTATION);
+
+ TypeSystem ts = CasCreationUtils.createCas(tsd, null, null, null).getTypeSystem();
+
+ String json = mapper.writer() //
+ .withAttribute(ReferenceCache.KEY, ReferenceCache.builder().build()) //
+ .writeValueAsString(ts.getType("Type1"));
+
+ TypeDescription tdActual = mapper.reader() //
+ .forType(TypeDescription.class) //
+ .readValue(json);
+
+ assertThat(tdActual).isEqualTo(tdExpected);
+ }
+
+ private ObjectMapper getMapper() {
+ SimpleModule module = new SimpleModule("UIMA CAS JSON", new Version(1, 0, 0, null, null, null));
+
+ module.addSerializer(Type.class, new TypeSerializer());
+ module.addDeserializer(TypeDescription.class, new TypeDeserializer());
+ module.addSerializer(Feature.class, new FeatureSerializer());
+ module.addDeserializer(FeatureDescription.class, new FeatureDeserializer());
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(module);
+ return mapper;
+ }
+}
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/data.json
new file mode 100644
index 0000000..422cea5
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/data.json
@@ -0,0 +1,78 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : 1,
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "\uD83E\uDD73 This \uD83D\uDC73\uD83C\uDFFB♀️ is ✆ a \uD83E\uDDD4\uD83C\uDFFE♂️ test \uD83D\uDC7B"
+ }, {
+ "%ID" : 2,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 2
+ }, {
+ "%ID" : 3,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 3,
+ "end" : 7
+ }, {
+ "%ID" : 4,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 8,
+ "end" : 15
+ }, {
+ "%ID" : 5,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 16,
+ "end" : 18
+ }, {
+ "%ID" : 6,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 19,
+ "end" : 20
+ }, {
+ "%ID" : 7,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 21,
+ "end" : 22
+ }, {
+ "%ID" : 8,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 23,
+ "end" : 30
+ }, {
+ "%ID" : 9,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 31,
+ "end" : 35
+ }, {
+ "%ID" : 10,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 36,
+ "end" : 38
+ }, {
+ "%ID" : 11,
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 38,
+ "language" : "x-unspecified"
+ } ],
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug.xmi
new file mode 100644
index 0000000..6d8ec43
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug.xmi
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="2"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="3" end="7"/>
+ <tcas:Annotation xmi:id="4" sofa="1" begin="8" end="15"/>
+ <tcas:Annotation xmi:id="5" sofa="1" begin="16" end="18"/>
+ <tcas:Annotation xmi:id="6" sofa="1" begin="19" end="20"/>
+ <tcas:Annotation xmi:id="7" sofa="1" begin="21" end="22"/>
+ <tcas:Annotation xmi:id="8" sofa="1" begin="23" end="30"/>
+ <tcas:Annotation xmi:id="9" sofa="1" begin="31" end="35"/>
+ <tcas:Annotation xmi:id="10" sofa="1" begin="36" end="38"/>
+ <tcas:DocumentAnnotation xmi:id="11" sofa="1" begin="0" end="38" language="x-unspecified"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text" sofaString="🥳 This 👳🏻♀️ is ✆ a 🧔🏾♂️ test 👻"/>
+ <cas:View sofa="1" members="2 3 4 5 6 7 8 9 10 11"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/data.json
new file mode 100644
index 0000000..206f7d4
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/data.json
@@ -0,0 +1,76 @@
+{
+ "%TYPES" : {
+ "SpecialValuesType" : {
+ "%NAME" : "SpecialValuesType",
+ "%SUPER_TYPE" : "uima.cas.TOP",
+ "doubleZero" : {
+ "%NAME" : "doubleZero",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "doubleOne" : {
+ "%NAME" : "doubleOne",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "doublePosInfinity" : {
+ "%NAME" : "doublePosInfinity",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "doubleNegInfinity" : {
+ "%NAME" : "doubleNegInfinity",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "doubleNan" : {
+ "%NAME" : "doubleNan",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "floatZero" : {
+ "%NAME" : "floatZero",
+ "%RANGE" : "uima.cas.Float"
+ },
+ "floatOne" : {
+ "%NAME" : "floatOne",
+ "%RANGE" : "uima.cas.Float"
+ },
+ "floatPosInfinity" : {
+ "%NAME" : "floatPosInfinity",
+ "%RANGE" : "uima.cas.Float"
+ },
+ "floatNegInfinity" : {
+ "%NAME" : "floatNegInfinity",
+ "%RANGE" : "uima.cas.Float"
+ },
+ "floatNan" : {
+ "%NAME" : "floatNan",
+ "%RANGE" : "uima.cas.Float"
+ }
+ }
+ },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : 1,
+ "%TYPE" : "SpecialValuesType",
+ "doubleZero" : 0.0,
+ "doubleOne" : 1.0,
+ "#doublePosInfinity" : "Infinity",
+ "#doubleNegInfinity" : "-Infinity",
+ "#doubleNan" : "NaN",
+ "floatZero" : 0.0,
+ "floatOne" : 1.0,
+ "#floatPosInfinity" : "Infinity",
+ "#floatNegInfinity" : "-Infinity",
+ "#floatNan" : "NaN"
+ }, {
+ "%ID" : 2,
+ "%TYPE" : "uima.cas.DoubleArray",
+ "%ELEMENTS" : [ 0.0, 1.0, "-Infinity", "Infinity", "NaN" ]
+ }, {
+ "%ID" : 3,
+ "%TYPE" : "uima.cas.FloatArray",
+ "%ELEMENTS" : [ 0.0, 1.0, "-Infinity", "Infinity", "NaN" ]
+ } ],
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 4,
+ "%MEMBERS" : [ 1, 2, 3 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/debug-typesystem.xml
new file mode 100644
index 0000000..9a8766d
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/debug-typesystem.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ <typeDescription>
+ <name>SpecialValuesType</name>
+ <description/>
+ <supertypeName>uima.cas.TOP</supertypeName>
+ <features>
+ <featureDescription>
+ <name>doubleZero</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleOne</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doublePosInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleNegInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleNan</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatZero</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatOne</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatPosInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatNegInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatNan</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/debug.xmi
new file mode 100644
index 0000000..e02d4cb
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/debug.xmi
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:noNamespace="http:///uima/noNamespace.ecore" xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <noNamespace:SpecialValuesType xmi:id="1" doubleZero="0.0" doubleOne="1.0" doublePosInfinity="Infinity" doubleNegInfinity="-Infinity" doubleNan="NaN" floatZero="0.0" floatOne="1.0" floatPosInfinity="Infinity" floatNegInfinity="-Infinity" floatNan="NaN"/>
+ <cas:DoubleArray xmi:id="2" elements="0.0 1.0 -Infinity Infinity NaN"/>
+ <cas:FloatArray xmi:id="3" elements="0.0 1.0 -Infinity Infinity NaN"/>
+ <cas:View members="1 2 3"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/typesystem.xml
new file mode 100644
index 0000000..9a8766d
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithFloatingPointSpecialValues/typesystem.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ <typeDescription>
+ <name>SpecialValuesType</name>
+ <description/>
+ <supertypeName>uima.cas.TOP</supertypeName>
+ <features>
+ <featureDescription>
+ <name>doubleZero</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleOne</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doublePosInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleNegInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleNan</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatZero</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatOne</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatPosInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatNegInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatNan</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithLeftToRightTextAndAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithLeftToRightTextAndAnnotations/data.json
new file mode 100644
index 0000000..1944181
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithLeftToRightTextAndAnnotations/data.json
@@ -0,0 +1,36 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : 1,
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "هذا اختبار"
+ }, {
+ "%ID" : 2,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 3
+ }, {
+ "%ID" : 3,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 4,
+ "end" : 10
+ }, {
+ "%ID" : 4,
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 10,
+ "language" : "x-unspecified"
+ } ],
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug.xmi
new file mode 100644
index 0000000..108d362
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug.xmi
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="3"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="4" end="10"/>
+ <tcas:DocumentAnnotation xmi:id="4" sofa="1" begin="0" end="10" language="x-unspecified"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text" sofaString="هذا اختبار"/>
+ <cas:View sofa="1" members="2 3 4"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataArray/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataArray/data.json
new file mode 100644
index 0000000..20d935b
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataArray/data.json
@@ -0,0 +1,21 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : 1,
+ "%TYPE" : "uima.cas.ByteArray",
+ "%ELEMENTS" : "VGhpcyBpcyBhIHRlc3Q="
+ }, {
+ "%ID" : 2,
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text/plain",
+ "@sofaArray" : 1
+ } ],
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 2,
+ "%MEMBERS" : [ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataArray/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataArray/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataArray/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataArray/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataArray/debug.xmi
new file mode 100644
index 0000000..89075f6
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataArray/debug.xmi
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <cas:Sofa xmi:id="2" sofaNum="1" sofaID="_InitialView" mimeType="text/plain" sofaArray="1"/>
+ <cas:ByteArray xmi:id="1" elements="5468697320697320612074657374"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataURI/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataURI/data.json
new file mode 100644
index 0000000..0b142a8
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataURI/data.json
@@ -0,0 +1,17 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : 1,
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text/plain",
+ "sofaURI" : "classpath:/ProgrammaticallyCreatedCasDataSuite/document.txt"
+ } ],
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataURI/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataURI/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataURI/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataURI/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataURI/debug.xmi
new file mode 100644
index 0000000..89966e0
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithSofaDataURI/debug.xmi
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text/plain" sofaURI="classpath:/ProgrammaticallyCreatedCasDataSuite/document.txt"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithText/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithText/data.json
new file mode 100644
index 0000000..39f5ffe
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithText/data.json
@@ -0,0 +1,24 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : 1,
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "This is a test."
+ }, {
+ "%ID" : 2,
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 15,
+ "language" : "x-unspecified"
+ } ],
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithText/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithText/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithText/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithText/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithText/debug.xmi
new file mode 100644
index 0000000..943df5f
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithText/debug.xmi
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:DocumentAnnotation xmi:id="2" sofa="1" begin="0" end="15" language="x-unspecified"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text" sofaString="This is a test."/>
+ <cas:View sofa="1" members="2"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTextAndAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTextAndAnnotations/data.json
new file mode 100644
index 0000000..a9522cf
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTextAndAnnotations/data.json
@@ -0,0 +1,48 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : 1,
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "This is a test"
+ }, {
+ "%ID" : 2,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 4
+ }, {
+ "%ID" : 3,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 5,
+ "end" : 7
+ }, {
+ "%ID" : 4,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 8,
+ "end" : 9
+ }, {
+ "%ID" : 5,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 10,
+ "end" : 14
+ }, {
+ "%ID" : 6,
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 14,
+ "language" : "x-unspecified"
+ } ],
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4, 5, 6 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTextAndAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTextAndAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTextAndAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTextAndAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTextAndAnnotations/debug.xmi
new file mode 100644
index 0000000..37c1e9b
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTextAndAnnotations/debug.xmi
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="4"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="5" end="7"/>
+ <tcas:Annotation xmi:id="4" sofa="1" begin="8" end="9"/>
+ <tcas:Annotation xmi:id="5" sofa="1" begin="10" end="14"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView"/>
+ <cas:View sofa="1" members="2 3 4 5"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/data.json
new file mode 100644
index 0000000..d586738
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/data.json
@@ -0,0 +1,48 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : 1,
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "這是一個測試"
+ }, {
+ "%ID" : 2,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 1
+ }, {
+ "%ID" : 3,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 1,
+ "end" : 2
+ }, {
+ "%ID" : 4,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 2,
+ "end" : 4
+ }, {
+ "%ID" : 5,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 4,
+ "end" : 6
+ }, {
+ "%ID" : 6,
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 6,
+ "language" : "x-unspecified"
+ } ],
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4, 5, 6 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug.xmi
new file mode 100644
index 0000000..0087d72
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug.xmi
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="1"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="1" end="2"/>
+ <tcas:Annotation xmi:id="4" sofa="1" begin="2" end="4"/>
+ <tcas:Annotation xmi:id="5" sofa="1" begin="4" end="6"/>
+ <tcas:DocumentAnnotation xmi:id="6" sofa="1" begin="0" end="6" language="x-unspecified"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text" sofaString="這是一個測試"/>
+ <cas:View sofa="1" members="2 3 4 5 6"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithoutTextButWithAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithoutTextButWithAnnotations/data.json
new file mode 100644
index 0000000..56784fe
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithoutTextButWithAnnotations/data.json
@@ -0,0 +1,39 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : 1,
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView"
+ }, {
+ "%ID" : 2,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 4
+ }, {
+ "%ID" : 3,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 5,
+ "end" : 7
+ }, {
+ "%ID" : 4,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 8,
+ "end" : 9
+ }, {
+ "%ID" : 5,
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 10,
+ "end" : 14
+ } ],
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4, 5 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithoutTextButWithAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithoutTextButWithAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithoutTextButWithAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithoutTextButWithAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithoutTextButWithAnnotations/debug.xmi
new file mode 100644
index 0000000..37c1e9b
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/casWithoutTextButWithAnnotations/debug.xmi
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="4"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="5" end="7"/>
+ <tcas:Annotation xmi:id="4" sofa="1" begin="8" end="9"/>
+ <tcas:Annotation xmi:id="5" sofa="1" begin="10" end="14"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView"/>
+ <cas:View sofa="1" members="2 3 4 5"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/emptyCas/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/emptyCas/data.json
new file mode 100644
index 0000000..fcd8582
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/emptyCas/data.json
@@ -0,0 +1,9 @@
+{
+ "%TYPES" : { },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/emptyCas/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/emptyCas/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/emptyCas/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/emptyCas/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/emptyCas/debug.xmi
new file mode 100644
index 0000000..6fd88bd
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsArray_Test/ser-ref/emptyCas/debug.xmi
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/data.json
new file mode 100644
index 0000000..182ef8a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/data.json
@@ -0,0 +1,79 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : {
+ "1" : {
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "\uD83E\uDD73 This \uD83D\uDC73\uD83C\uDFFB♀️ is ✆ a \uD83E\uDDD4\uD83C\uDFFE♂️ test \uD83D\uDC7B"
+ },
+ "2" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 2
+ },
+ "3" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 3,
+ "end" : 7
+ },
+ "4" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 8,
+ "end" : 15
+ },
+ "5" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 16,
+ "end" : 18
+ },
+ "6" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 19,
+ "end" : 20
+ },
+ "7" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 21,
+ "end" : 22
+ },
+ "8" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 23,
+ "end" : 30
+ },
+ "9" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 31,
+ "end" : 35
+ },
+ "10" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 36,
+ "end" : 38
+ },
+ "11" : {
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 38,
+ "language" : "x-unspecified"
+ }
+ },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug.xmi
new file mode 100644
index 0000000..6d8ec43
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithEmojiUnicodeTextAndAnnotations/debug.xmi
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="2"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="3" end="7"/>
+ <tcas:Annotation xmi:id="4" sofa="1" begin="8" end="15"/>
+ <tcas:Annotation xmi:id="5" sofa="1" begin="16" end="18"/>
+ <tcas:Annotation xmi:id="6" sofa="1" begin="19" end="20"/>
+ <tcas:Annotation xmi:id="7" sofa="1" begin="21" end="22"/>
+ <tcas:Annotation xmi:id="8" sofa="1" begin="23" end="30"/>
+ <tcas:Annotation xmi:id="9" sofa="1" begin="31" end="35"/>
+ <tcas:Annotation xmi:id="10" sofa="1" begin="36" end="38"/>
+ <tcas:DocumentAnnotation xmi:id="11" sofa="1" begin="0" end="38" language="x-unspecified"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text" sofaString="🥳 This 👳🏻♀️ is ✆ a 🧔🏾♂️ test 👻"/>
+ <cas:View sofa="1" members="2 3 4 5 6 7 8 9 10 11"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/data.json
new file mode 100644
index 0000000..c9028a9
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/data.json
@@ -0,0 +1,77 @@
+{
+ "%TYPES" : {
+ "SpecialValuesType" : {
+ "%NAME" : "SpecialValuesType",
+ "%SUPER_TYPE" : "uima.cas.TOP",
+ "doubleZero" : {
+ "%NAME" : "doubleZero",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "doubleOne" : {
+ "%NAME" : "doubleOne",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "doublePosInfinity" : {
+ "%NAME" : "doublePosInfinity",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "doubleNegInfinity" : {
+ "%NAME" : "doubleNegInfinity",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "doubleNan" : {
+ "%NAME" : "doubleNan",
+ "%RANGE" : "uima.cas.Double"
+ },
+ "floatZero" : {
+ "%NAME" : "floatZero",
+ "%RANGE" : "uima.cas.Float"
+ },
+ "floatOne" : {
+ "%NAME" : "floatOne",
+ "%RANGE" : "uima.cas.Float"
+ },
+ "floatPosInfinity" : {
+ "%NAME" : "floatPosInfinity",
+ "%RANGE" : "uima.cas.Float"
+ },
+ "floatNegInfinity" : {
+ "%NAME" : "floatNegInfinity",
+ "%RANGE" : "uima.cas.Float"
+ },
+ "floatNan" : {
+ "%NAME" : "floatNan",
+ "%RANGE" : "uima.cas.Float"
+ }
+ }
+ },
+ "%FEATURE_STRUCTURES" : {
+ "1" : {
+ "%TYPE" : "SpecialValuesType",
+ "doubleZero" : 0.0,
+ "doubleOne" : 1.0,
+ "#doublePosInfinity" : "Infinity",
+ "#doubleNegInfinity" : "-Infinity",
+ "#doubleNan" : "NaN",
+ "floatZero" : 0.0,
+ "floatOne" : 1.0,
+ "#floatPosInfinity" : "Infinity",
+ "#floatNegInfinity" : "-Infinity",
+ "#floatNan" : "NaN"
+ },
+ "2" : {
+ "%TYPE" : "uima.cas.DoubleArray",
+ "%ELEMENTS" : [ 0.0, 1.0, "-Infinity", "Infinity", "NaN" ]
+ },
+ "3" : {
+ "%TYPE" : "uima.cas.FloatArray",
+ "%ELEMENTS" : [ 0.0, 1.0, "-Infinity", "Infinity", "NaN" ]
+ }
+ },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 4,
+ "%MEMBERS" : [ 1, 2, 3 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/debug-typesystem.xml
new file mode 100644
index 0000000..9a8766d
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/debug-typesystem.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ <typeDescription>
+ <name>SpecialValuesType</name>
+ <description/>
+ <supertypeName>uima.cas.TOP</supertypeName>
+ <features>
+ <featureDescription>
+ <name>doubleZero</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleOne</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doublePosInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleNegInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleNan</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatZero</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatOne</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatPosInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatNegInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatNan</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/debug.xmi
new file mode 100644
index 0000000..e02d4cb
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/debug.xmi
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:noNamespace="http:///uima/noNamespace.ecore" xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <noNamespace:SpecialValuesType xmi:id="1" doubleZero="0.0" doubleOne="1.0" doublePosInfinity="Infinity" doubleNegInfinity="-Infinity" doubleNan="NaN" floatZero="0.0" floatOne="1.0" floatPosInfinity="Infinity" floatNegInfinity="-Infinity" floatNan="NaN"/>
+ <cas:DoubleArray xmi:id="2" elements="0.0 1.0 -Infinity Infinity NaN"/>
+ <cas:FloatArray xmi:id="3" elements="0.0 1.0 -Infinity Infinity NaN"/>
+ <cas:View members="1 2 3"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/typesystem.xml
new file mode 100644
index 0000000..9a8766d
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithFloatingPointSpecialValues/typesystem.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ <typeDescription>
+ <name>SpecialValuesType</name>
+ <description/>
+ <supertypeName>uima.cas.TOP</supertypeName>
+ <features>
+ <featureDescription>
+ <name>doubleZero</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleOne</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doublePosInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleNegInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>doubleNan</name>
+ <description/>
+ <rangeTypeName>uima.cas.Double</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatZero</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatOne</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatPosInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatNegInfinity</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ <featureDescription>
+ <name>floatNan</name>
+ <description/>
+ <rangeTypeName>uima.cas.Float</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithLeftToRightTextAndAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithLeftToRightTextAndAnnotations/data.json
new file mode 100644
index 0000000..dfbbd65
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithLeftToRightTextAndAnnotations/data.json
@@ -0,0 +1,37 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : {
+ "1" : {
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "هذا اختبار"
+ },
+ "2" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 3
+ },
+ "3" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 4,
+ "end" : 10
+ },
+ "4" : {
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 10,
+ "language" : "x-unspecified"
+ }
+ },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug.xmi
new file mode 100644
index 0000000..108d362
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithLeftToRightTextAndAnnotations/debug.xmi
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="3"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="4" end="10"/>
+ <tcas:DocumentAnnotation xmi:id="4" sofa="1" begin="0" end="10" language="x-unspecified"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text" sofaString="هذا اختبار"/>
+ <cas:View sofa="1" members="2 3 4"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataArray/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataArray/data.json
new file mode 100644
index 0000000..b2e39d2
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataArray/data.json
@@ -0,0 +1,22 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : {
+ "1" : {
+ "%TYPE" : "uima.cas.ByteArray",
+ "%ELEMENTS" : "VGhpcyBpcyBhIHRlc3Q="
+ },
+ "2" : {
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text/plain",
+ "@sofaArray" : 1
+ }
+ },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 2,
+ "%MEMBERS" : [ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataArray/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataArray/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataArray/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataArray/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataArray/debug.xmi
new file mode 100644
index 0000000..89075f6
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataArray/debug.xmi
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <cas:Sofa xmi:id="2" sofaNum="1" sofaID="_InitialView" mimeType="text/plain" sofaArray="1"/>
+ <cas:ByteArray xmi:id="1" elements="5468697320697320612074657374"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataURI/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataURI/data.json
new file mode 100644
index 0000000..a02bb76
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataURI/data.json
@@ -0,0 +1,18 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : {
+ "1" : {
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text/plain",
+ "sofaURI" : "classpath:/ProgrammaticallyCreatedCasDataSuite/document.txt"
+ }
+ },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataURI/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataURI/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataURI/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataURI/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataURI/debug.xmi
new file mode 100644
index 0000000..89966e0
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithSofaDataURI/debug.xmi
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text/plain" sofaURI="classpath:/ProgrammaticallyCreatedCasDataSuite/document.txt"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithText/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithText/data.json
new file mode 100644
index 0000000..4d32065
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithText/data.json
@@ -0,0 +1,25 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : {
+ "1" : {
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "This is a test."
+ },
+ "2" : {
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 15,
+ "language" : "x-unspecified"
+ }
+ },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithText/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithText/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithText/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithText/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithText/debug.xmi
new file mode 100644
index 0000000..943df5f
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithText/debug.xmi
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:DocumentAnnotation xmi:id="2" sofa="1" begin="0" end="15" language="x-unspecified"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text" sofaString="This is a test."/>
+ <cas:View sofa="1" members="2"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTextAndAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTextAndAnnotations/data.json
new file mode 100644
index 0000000..6df3290
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTextAndAnnotations/data.json
@@ -0,0 +1,49 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : {
+ "1" : {
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "This is a test"
+ },
+ "2" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 4
+ },
+ "3" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 5,
+ "end" : 7
+ },
+ "4" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 8,
+ "end" : 9
+ },
+ "5" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 10,
+ "end" : 14
+ },
+ "6" : {
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 14,
+ "language" : "x-unspecified"
+ }
+ },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4, 5, 6 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTextAndAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTextAndAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTextAndAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTextAndAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTextAndAnnotations/debug.xmi
new file mode 100644
index 0000000..37c1e9b
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTextAndAnnotations/debug.xmi
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="4"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="5" end="7"/>
+ <tcas:Annotation xmi:id="4" sofa="1" begin="8" end="9"/>
+ <tcas:Annotation xmi:id="5" sofa="1" begin="10" end="14"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView"/>
+ <cas:View sofa="1" members="2 3 4 5"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/data.json
new file mode 100644
index 0000000..a90d82c
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/data.json
@@ -0,0 +1,49 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : {
+ "1" : {
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "這是一個測試"
+ },
+ "2" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 1
+ },
+ "3" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 1,
+ "end" : 2
+ },
+ "4" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 2,
+ "end" : 4
+ },
+ "5" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 4,
+ "end" : 6
+ },
+ "6" : {
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 6,
+ "language" : "x-unspecified"
+ }
+ },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4, 5, 6 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug.xmi
new file mode 100644
index 0000000..0087d72
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithTraditionalChineseTextAndAnnotations/debug.xmi
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="1"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="1" end="2"/>
+ <tcas:Annotation xmi:id="4" sofa="1" begin="2" end="4"/>
+ <tcas:Annotation xmi:id="5" sofa="1" begin="4" end="6"/>
+ <tcas:DocumentAnnotation xmi:id="6" sofa="1" begin="0" end="6" language="x-unspecified"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView" mimeType="text" sofaString="這是一個測試"/>
+ <cas:View sofa="1" members="2 3 4 5 6"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithoutTextButWithAnnotations/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithoutTextButWithAnnotations/data.json
new file mode 100644
index 0000000..25a71c9
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithoutTextButWithAnnotations/data.json
@@ -0,0 +1,40 @@
+{
+ "%TYPES" : { },
+ "%FEATURE_STRUCTURES" : {
+ "1" : {
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView"
+ },
+ "2" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 0,
+ "end" : 4
+ },
+ "3" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 5,
+ "end" : 7
+ },
+ "4" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 8,
+ "end" : 9
+ },
+ "5" : {
+ "%TYPE" : "uima.tcas.Annotation",
+ "@sofa" : 1,
+ "begin" : 10,
+ "end" : 14
+ }
+ },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ 2, 3, 4, 5 ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithoutTextButWithAnnotations/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithoutTextButWithAnnotations/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithoutTextButWithAnnotations/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithoutTextButWithAnnotations/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithoutTextButWithAnnotations/debug.xmi
new file mode 100644
index 0000000..37c1e9b
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/casWithoutTextButWithAnnotations/debug.xmi
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+ <tcas:Annotation xmi:id="2" sofa="1" begin="0" end="4"/>
+ <tcas:Annotation xmi:id="3" sofa="1" begin="5" end="7"/>
+ <tcas:Annotation xmi:id="4" sofa="1" begin="8" end="9"/>
+ <tcas:Annotation xmi:id="5" sofa="1" begin="10" end="14"/>
+ <cas:Sofa xmi:id="1" sofaNum="1" sofaID="_InitialView"/>
+ <cas:View sofa="1" members="2 3 4 5"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/emptyCas/data.json b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/emptyCas/data.json
new file mode 100644
index 0000000..fcd8582
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/emptyCas/data.json
@@ -0,0 +1,9 @@
+{
+ "%TYPES" : { },
+ "%VIEWS" : {
+ "_InitialView" : {
+ "%SOFA" : 1,
+ "%MEMBERS" : [ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/emptyCas/debug-typesystem.xml b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/emptyCas/debug-typesystem.xml
new file mode 100644
index 0000000..07e327a
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/emptyCas/debug-typesystem.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<typeSystemDescription xmlns="http://uima.apache.org/resourceSpecifier">
+ <types>
+ <typeDescription>
+ <name>uima.tcas.DocumentAnnotation</name>
+ <description/>
+ <supertypeName>uima.tcas.Annotation</supertypeName>
+ <features>
+ <featureDescription>
+ <name>language</name>
+ <description/>
+ <rangeTypeName>uima.cas.String</rangeTypeName>
+ </featureDescription>
+ </features>
+ </typeDescription>
+ </types>
+</typeSystemDescription>
diff --git a/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/emptyCas/debug.xmi b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/emptyCas/debug.xmi
new file mode 100644
index 0000000..6fd88bd
--- /dev/null
+++ b/uimaj-json/src/test/resources/CasSerializationDeserialization_JsonCas2_FsAsObject_Test/ser-ref/emptyCas/debug.xmi
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?><xmi:XMI xmlns:tcas="http:///uima/tcas.ecore" xmlns:xmi="http://www.omg.org/XMI" xmlns:cas="http:///uima/cas.ecore" xmi:version="2.0">
+ <cas:NULL xmi:id="0"/>
+</xmi:XMI>
diff --git a/uimaj-json/src/test/resources/FlexJsonDeserializer/feature_structures_only.json b/uimaj-json/src/test/resources/FlexJsonDeserializer/feature_structures_only.json
new file mode 100644
index 0000000..66769ed
--- /dev/null
+++ b/uimaj-json/src/test/resources/FlexJsonDeserializer/feature_structures_only.json
@@ -0,0 +1,23 @@
+[ {
+ "%ID" : "0",
+ "%TYPE" : "Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "Hello world"
+}, {
+ "%ID" : "1",
+ "%TYPE" : "Annotation",
+ "%VIEWS" : [ "_InitialView" ],
+ "@sofa" : "0",
+ "begin" : 0,
+ "end" : 10
+}, {
+ "%ID" : "2",
+ "%TYPE" : "DocumentAnnotation",
+ "%VIEWS" : [ "_InitialView" ],
+ "@sofa" : "0",
+ "begin" : 0,
+ "end" : 11,
+ "language" : "en"
+} ]
diff --git a/uimaj-json/src/test/resources/FlexJsonDeserializer/text_only.json b/uimaj-json/src/test/resources/FlexJsonDeserializer/text_only.json
new file mode 100644
index 0000000..6bdab32
--- /dev/null
+++ b/uimaj-json/src/test/resources/FlexJsonDeserializer/text_only.json
@@ -0,0 +1 @@
+"Hello world."
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/FlexJsonSerializerTest/customAnnotationType/reference.json b/uimaj-json/src/test/resources/FlexJsonSerializerTest/customAnnotationType/reference.json
new file mode 100644
index 0000000..dd44fc2
--- /dev/null
+++ b/uimaj-json/src/test/resources/FlexJsonSerializerTest/customAnnotationType/reference.json
@@ -0,0 +1,21 @@
+{
+ "%TYPES" : {
+ "custom.Annotation" : {
+ "%SUPER_TYPE" : "uima.tcas.Annotation",
+ "value" : "uima.cas.String"
+ }
+ },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : "0",
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView"
+ }, {
+ "%ID" : "1",
+ "%TYPE" : "custom.Annotation",
+ "%VIEWS" : [ "_InitialView" ],
+ "@sofa" : "0",
+ "begin" : 0,
+ "end" : 10
+ } ]
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/FlexJsonSerializerTest/featureStructureIndexedInMultipleViewsInline/reference.json b/uimaj-json/src/test/resources/FlexJsonSerializerTest/featureStructureIndexedInMultipleViewsInline/reference.json
new file mode 100644
index 0000000..571f94b
--- /dev/null
+++ b/uimaj-json/src/test/resources/FlexJsonSerializerTest/featureStructureIndexedInMultipleViewsInline/reference.json
@@ -0,0 +1,39 @@
+{
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : "0",
+ "%TYPE" : "uima.cas.TOP",
+ "%VIEWS" : [ "_InitialView", "secondView" ]
+ }, {
+ "%ID" : "1",
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "First view"
+ }, {
+ "%ID" : "2",
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "%VIEWS" : [ "_InitialView" ],
+ "%FLAGS" : [ "DocumentAnnotation" ],
+ "@sofa" : "1",
+ "begin" : 0,
+ "end" : 10,
+ "language" : "x-unspecified"
+ }, {
+ "%ID" : "3",
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 2,
+ "sofaID" : "secondView",
+ "mimeType" : "text",
+ "sofaString" : "Second view"
+ }, {
+ "%ID" : "4",
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "%VIEWS" : [ "secondView" ],
+ "%FLAGS" : [ "DocumentAnnotation" ],
+ "@sofa" : "3",
+ "begin" : 0,
+ "end" : 11,
+ "language" : "x-unspecified"
+ } ]
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/FlexJsonSerializerTest/featureStructureIndexedInMultipleViewsSeparate/reference.json b/uimaj-json/src/test/resources/FlexJsonSerializerTest/featureStructureIndexedInMultipleViewsSeparate/reference.json
new file mode 100644
index 0000000..e3d7825
--- /dev/null
+++ b/uimaj-json/src/test/resources/FlexJsonSerializerTest/featureStructureIndexedInMultipleViewsSeparate/reference.json
@@ -0,0 +1,40 @@
+{
+ "%VIEWS" : {
+ "_InitialView" : [ "0", "1", "2" ],
+ "secondView" : [ "3", "4", "2" ]
+ },
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : "2",
+ "%TYPE" : "uima.cas.TOP"
+ }, {
+ "%ID" : "0",
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "First view"
+ }, {
+ "%ID" : "1",
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "%FLAGS" : [ "DocumentAnnotation" ],
+ "@sofa" : "0",
+ "begin" : 0,
+ "end" : 10,
+ "language" : "x-unspecified"
+ }, {
+ "%ID" : "3",
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 2,
+ "sofaID" : "secondView",
+ "mimeType" : "text",
+ "sofaString" : "Second view"
+ }, {
+ "%ID" : "4",
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "%FLAGS" : [ "DocumentAnnotation" ],
+ "@sofa" : "3",
+ "begin" : 0,
+ "end" : 11,
+ "language" : "x-unspecified"
+ } ]
+}
\ No newline at end of file
diff --git a/uimaj-json/src/test/resources/FlexJsonSerializerTest/multipleViewsAndSofas/reference.json b/uimaj-json/src/test/resources/FlexJsonSerializerTest/multipleViewsAndSofas/reference.json
new file mode 100644
index 0000000..33c5fea
--- /dev/null
+++ b/uimaj-json/src/test/resources/FlexJsonSerializerTest/multipleViewsAndSofas/reference.json
@@ -0,0 +1,35 @@
+{
+ "%FEATURE_STRUCTURES" : [ {
+ "%ID" : "0",
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 1,
+ "sofaID" : "_InitialView",
+ "mimeType" : "text",
+ "sofaString" : "First view"
+ }, {
+ "%ID" : "1",
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "%VIEWS" : [ "_InitialView" ],
+ "%FLAGS" : [ "DocumentAnnotation" ],
+ "@sofa" : "0",
+ "begin" : 0,
+ "end" : 10,
+ "language" : "x-unspecified"
+ }, {
+ "%ID" : "2",
+ "%TYPE" : "uima.cas.Sofa",
+ "sofaNum" : 2,
+ "sofaID" : "secondView",
+ "mimeType" : "text",
+ "sofaString" : "Second view"
+ }, {
+ "%ID" : "3",
+ "%TYPE" : "uima.tcas.DocumentAnnotation",
+ "%VIEWS" : [ "secondView" ],
+ "%FLAGS" : [ "DocumentAnnotation" ],
+ "@sofa" : "2",
+ "begin" : 0,
+ "end" : 11,
+ "language" : "x-unspecified"
+ } ]
+}
\ No newline at end of file
diff --git a/uimaj-parent/pom.xml b/uimaj-parent/pom.xml
index 8701bbe..8f6a36e 100644
--- a/uimaj-parent/pom.xml
+++ b/uimaj-parent/pom.xml
@@ -153,7 +153,7 @@
-->
<slf4j.version>1.7.25</slf4j.version>
<log4j.version>2.10.0</log4j.version>
- <jackson.version>2.9.2</jackson.version>
+ <jackson.version>2.12.4</jackson.version>
<junit.version>5.7.2</junit.version>
<maven.compiler.target>1.8</maven.compiler.target>