| /* |
| * 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; |
| } |
| } |