Package com.opengamma.web.json

Source Code of com.opengamma.web.json.FudgeMsgJSONWriter

/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.web.json;

import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.fudgemsg.FudgeContext;
import org.fudgemsg.FudgeField;
import org.fudgemsg.FudgeFieldType;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.FudgeMsgEnvelope;
import org.fudgemsg.FudgeRuntimeException;
import org.fudgemsg.taxonomy.FudgeTaxonomy;
import org.fudgemsg.types.SecondaryFieldTypeBase;
import org.fudgemsg.wire.FudgeRuntimeIOException;
import org.fudgemsg.wire.FudgeSize;
import org.fudgemsg.wire.json.FudgeJSONSettings;
import org.fudgemsg.wire.types.FudgeWireType;
import org.json.JSONException;
import org.json.JSONWriter;

import com.google.common.collect.Lists;

/**
* A Fudge writer that produces JSON.
* <p>
* This writer writes a Fudge message as JSON.
* This can be used for JSON output, or can be used to assist in developing/debugging
* a streaming serializer without having to inspect the binary output.
* <p>
* Please refer to <a href="http://wiki.fudgemsg.org/display/FDG/JSON+Fudge+Messages">JSON Fudge Messages</a>
* for details on the representation.
*/
public class FudgeMsgJSONWriter implements Flushable, Closeable {
 
  private static final String BLANK_FIELD_NAME = "";
  /**
   * The taxonomy identifier to use for any messages that are passed without envelopes.
   */
  private static final short DEFAULT_TAXONOMY_ID = 0;
  /**
   * The schema version to add to the envelope header for any messages that are passed without envelopes.
   */
  private static final int DEFAULT_MESSAGE_VERSION = 0;
  /**
   * The processing directive flags to add to the envelope header for any messages that are passed without envelopes.
   */
  private static final int DEFAULT_MESSAGE_PROCESSING_DIRECTIVES = 0;
 
  /**
   * The fudge context
   */
  private final FudgeContext _fudgeContext;
   
  /**
   * The JSON settings.
   */
  private final FudgeJSONSettings _settings;
  /**
   * The underlying writer.
   */
  private final Writer _underlyingWriter;
  /**
   * The JSON writer.
   */
  private JSONWriter _writer;
 
  /**
   * Creates a new instance for writing a Fudge stream to a JSON writer.
   *
   * @param fudgeContext  the Fudge context, not null
   * @param writer  the underlying writer, not null
   */
  public FudgeMsgJSONWriter(final FudgeContext fudgeContext, final Writer writer) {
    this(fudgeContext, writer, new FudgeJSONSettings());
  }

  /**
   * Creates a new stream writer for writing Fudge messages in JSON format to a given
   * {@link Writer}.
   *
   * @param fudgeContext  the Fudge context, not null
   * @param writer  the underlying writer, not null
   * @param settings  the JSON settings, not null
   */
  public FudgeMsgJSONWriter(final FudgeContext fudgeContext, final Writer writer, final FudgeJSONSettings settings) {
    if (fudgeContext == null) {
      throw new NullPointerException("FudgeContext must not be null");
    }
    if (writer == null) {
      throw new NullPointerException("Writer must not be null");
    }
    if (settings == null) {
      throw new NullPointerException("FudgeJSONSettings must not be null");
    }
    _settings = settings;
    _underlyingWriter = writer;
    _fudgeContext = fudgeContext;
  }
   
  /**
   * Gets the taxonomy.
   * @return the taxonomy
   */
  private FudgeTaxonomy getTaxonomy(final int taxonomyId) {
    if (taxonomyId != DEFAULT_TAXONOMY_ID) {
      return getFudgeContext().getTaxonomyResolver().resolveTaxonomy((short) taxonomyId);
    }
    return null;
  }

  public FudgeContext getFudgeContext() {
    return _fudgeContext;
  }

  //-------------------------------------------------------------------------
  /**
   * Gets the JSON settings.
   *
   * @return the JSON settings, not null
   */
  public FudgeJSONSettings getSettings() {
    return _settings;
  }

  /**
   * Gets the JSON writer being used, allocating one if necessary.
   *
   * @return the writer, not null
   */
  private JSONWriter getWriter() {
    if (_writer == null) {
      _writer = new JSONWriter(getUnderlying());
    }
    return _writer;
  }

  /**
   * Discards the JSON writer.
   * The implementation only allows a single use so we must drop the instance
   * after each message envelope completes.
   */
  private void clearWriter() {
    _writer = null;
  }

  /**
   * Gets the underlying {@link Writer} that is wrapped by {@link JSONWriter} instances for messages.
   *
   * @return the writer, not null
   */
  public Writer getUnderlying() {
    return _underlyingWriter;
  }
 

  //-------------------------------------------------------------------------
  /**
   * Writes a message with the given taxonomy, schema version and processing directive flags.
   *
   * @param message  message to write
   * @param taxonomyId  identifier of the taxonomy to use. If the taxonomy is recognized by the {@link FudgeContext} it will be used to reduce field names to ordinals where possible.
   * @param version  schema version
   * @param processingDirectives  processing directive flags
   */
  public void writeMessage(final FudgeMsg message, final int taxonomyId, final int version, final int processingDirectives) {
    writeMessageEnvelope(new FudgeMsgEnvelope(message, version, processingDirectives), taxonomyId);
  }

  /**
   * Writes a message with the given taxonomy. Default schema version and processing directive flags are used.
   *
   * @param message  message to write
   * @param taxonomyId  the identifier of the taxonomy to use, if the taxonomy is recognized
   *   by the {@link FudgeContext} it will be used to reduce field names to ordinals where possible.
   */
  public void writeMessage(final FudgeMsg message, final int taxonomyId) {
    writeMessage(message, taxonomyId, DEFAULT_MESSAGE_VERSION, DEFAULT_MESSAGE_PROCESSING_DIRECTIVES);
  }

  /**
   * Writes a message. Default taxonomy, schema version and processing directive flags are used.
   *
   * @param message  message to write
   * @throws NullPointerException if the default taxonomy has not been specified
   */
  public void writeMessage(final FudgeMsg message) {
    writeMessage(message, DEFAULT_TAXONOMY_ID);
  }

  /**
   * Writes a message envelope with the given taxonomy.
   *
   * @param envelope message envelope to write
   * @param taxonomyId  the identifier of the taxonomy to use, if the taxonomy is recognized
   *   by the {@link FudgeContext} it will be used to reduce field names to ordinals where possible.
   */
  public void writeMessageEnvelope(final FudgeMsgEnvelope envelope, final int taxonomyId) {
    if (envelope == null) {
      return;
    }
    int messageSize = FudgeSize.calculateMessageEnvelopeSize(getTaxonomy(taxonomyId), envelope);
    writeEnvelopeHeader(envelope.getProcessingDirectives(), envelope.getVersion(), messageSize, taxonomyId);
    writeData(envelope.getMessage(), taxonomyId);
    writeMeta(envelope.getMessage(), taxonomyId);
    envelopeComplete();
  }

  private void writeMeta(FudgeMsg message, int taxonomyId) {
    try {
      getWriter().key("meta");
      getWriter().object();
    } catch (JSONException e) {
      wrapException("start of data", e);
    }
    writeFields(message, taxonomyId, true);
    try {
      getWriter().endObject();
    } catch (JSONException e) {
      wrapException("end of data", e);
    }
  }

  private void writeData(FudgeMsg message, int taxonomyId) {
    writeDataStart();
    writeFields(message, taxonomyId, false);
    writeDataEnd();
  }
 
  private void writeDataEnd() {
    try {
      getWriter().endObject();
    } catch (JSONException e) {
      wrapException("end of data", e);
    }
  }

  private void writeDataStart() {
    try {
      getWriter().key("data");
      getWriter().object();
    } catch (JSONException e) {
      wrapException("start of data", e);
    }
  }

  private void writeFields(Iterable<FudgeField> fudgeMsg, final int taxonomyId, boolean meta) {
    Map<String, List<FudgeField>> fieldName2Fields = new LinkedHashMap<String, List<FudgeField>>();
    for (FudgeField fudgeField : fudgeMsg) {
      if (fudgeField.getName() != null) {
        final List<FudgeField> fields = getFieldList(fieldName2Fields, fudgeField.getName());
        fields.add(fudgeField);
      } else if (fudgeField.getOrdinal() != null) {
        final List<FudgeField> fields = getFieldList(fieldName2Fields, fudgeField.getOrdinal().toString());
        fields.add(fudgeField);
      } else {
        final List<FudgeField> fields = getFieldList(fieldName2Fields, BLANK_FIELD_NAME);
        fields.add(fudgeField);
      }
    }
   
    for (Entry<String, List<FudgeField>> entry : fieldName2Fields.entrySet()) {
      List<FudgeField> fields = entry.getValue();
      if (!fields.isEmpty()) {
        if (fields.size() == 1) {
          FudgeField fudgeField = fields.get(0);
          writeField(fudgeField.getName(), fudgeField.getOrdinal(), fudgeField.getType(), fudgeField.getValue(), taxonomyId, meta);
        } else {
          writeRepeatedFields(fields, taxonomyId, meta);
        }
      }
    }
  }
 
  private void writeRepeatedFields(final List<FudgeField> fields, int taxonomyId, boolean meta) {
    FudgeField firstField = fields.iterator().next();
    try {
      String key = fudgeFieldStart(firstField.getOrdinal(), firstField.getName(), taxonomyId);
      if (key != null) {
        getWriter().array();
        for (FudgeField fudgeField : fields) {
          if (fudgeField.getType().getTypeId() == FudgeWireType.SUB_MESSAGE_TYPE_ID) {
            fudgeSubMessageStart();
            writeFields((FudgeMsg) fudgeField.getValue(), taxonomyId, meta);
            fudgeSubMessageEnd();
          } else {
            fudgeFieldValue(fudgeField.getType(), fudgeField.getValue(), meta);
          }
        }
        getWriter().endArray();
      }
    } catch (JSONException e) {
      wrapException("writing repeated fields", e);
    }
  }
   
  private void writeField(String name, Integer ordinal, FudgeFieldType type, Object fieldValue, final int taxonomyId, boolean meta) {
    if (fudgeFieldStart(ordinal, name, taxonomyId) != null) {
      if (type.getTypeId() == FudgeWireType.SUB_MESSAGE_TYPE_ID) {
        fudgeSubMessageStart();
        FudgeMsg subMsg = (FudgeMsg) fieldValue;
        writeFields(subMsg, taxonomyId, meta);
        fudgeSubMessageEnd();
      } else {
        fudgeFieldValue(type, fieldValue, meta);
      }
    }
  }
 
  @SuppressWarnings("unchecked")
  private void fudgeFieldValue(FudgeFieldType type, Object fieldValue, boolean meta) {
    try {
      if (meta) {
        String typeIdToString = getSettings().fudgeTypeIdToString(type.getTypeId());
        getWriter().value(typeIdToString);
      } else {
        if (type instanceof SecondaryFieldTypeBase<?, ?, ?>) {
          fieldValue = ((SecondaryFieldTypeBase<Object, Object, Object>) type).secondaryToPrimary(fieldValue);
        }
        switch (type.getTypeId()) {
          case FudgeWireType.INDICATOR_TYPE_ID:
            getWriter().value(null);
            break;
          case FudgeWireType.BYTE_ARRAY_TYPE_ID:
          case FudgeWireType.BYTE_ARRAY_4_TYPE_ID:
          case FudgeWireType.BYTE_ARRAY_8_TYPE_ID:
          case FudgeWireType.BYTE_ARRAY_16_TYPE_ID:
          case FudgeWireType.BYTE_ARRAY_20_TYPE_ID:
          case FudgeWireType.BYTE_ARRAY_32_TYPE_ID:
          case FudgeWireType.BYTE_ARRAY_64_TYPE_ID:
          case FudgeWireType.BYTE_ARRAY_128_TYPE_ID:
          case FudgeWireType.BYTE_ARRAY_256_TYPE_ID:
          case FudgeWireType.BYTE_ARRAY_512_TYPE_ID:
            writeArray((byte[]) fieldValue);
            break;
          case FudgeWireType.SHORT_ARRAY_TYPE_ID:
            writeArray((short[]) fieldValue);
            break;
          case FudgeWireType.INT_ARRAY_TYPE_ID:
            writeArray((int[]) fieldValue);
            break;
          case FudgeWireType.LONG_ARRAY_TYPE_ID:
            writeArray((long[]) fieldValue);
            break;
          case FudgeWireType.FLOAT_ARRAY_TYPE_ID:
            writeArray((float[]) fieldValue);
            break;
          case FudgeWireType.DOUBLE_ARRAY_TYPE_ID:
            writeArray((double[]) fieldValue);
            break;
          default:
            getWriter().value(fieldValue);
            break;
        }
      }
    } catch (JSONException e) {
      wrapException("field value", e);
    }
  }
 
  private void writeArray(final double[] data) throws JSONException {
    getWriter().array();
    for (int i = 0; i < data.length; i++) {
      getWriter().value(data[i]);
    }
    getWriter().endArray();
  }

  private void writeArray(final float[] data) throws JSONException {
    getWriter().array();
    for (int i = 0; i < data.length; i++) {
      getWriter().value(data[i]);
    }
    getWriter().endArray();
  }

  private void writeArray(final long[] data) throws JSONException {
    getWriter().array();
    for (int i = 0; i < data.length; i++) {
      getWriter().value(data[i]);
    }
    getWriter().endArray();
  }

  private void writeArray(final int[] data) throws JSONException {
    getWriter().array();
    for (int i = 0; i < data.length; i++) {
      getWriter().value(data[i]);
    }
    getWriter().endArray();

  }

  private void writeArray(final short[] data) throws JSONException {
    getWriter().array();
    for (int i = 0; i < data.length; i++) {
      getWriter().value(data[i]);
    }
    getWriter().endArray();
  }

  private void writeArray(final byte[] data) throws JSONException {
    getWriter().array();
    for (int i = 0; i < data.length; i++) {
      getWriter().value(data[i]);
    }
    getWriter().endArray();
  }
 
  /**
   * Ends the JSON sub-object.
   */
  private void fudgeSubMessageEnd() {
    try {
      getWriter().endObject();
    } catch (JSONException e) {
      wrapException("end of submessage", e);
    }
  }
 
  /**
   * Starts a sub-object within the JSON object. 
   */
  private void fudgeSubMessageStart() {
    try {
      getWriter().object();
    } catch (JSONException e) {
      wrapException("start of submessage", e);
    }
  }
 
  private String fudgeFieldStart(final Integer ordinal, final String name, final int taxonomyId) {
    String fieldName = null;   
    try {
      if (getSettings().getPreserveFieldNames()) {
        if (name != null) {
          getWriter().key(name);
          fieldName = name;
        } else if (ordinal != null) {
          fieldName = getFieldNameByOrdinal(ordinal, taxonomyId);
          getWriter().key(fieldName);
        } else {
          getWriter().key(BLANK_FIELD_NAME);
          fieldName = BLANK_FIELD_NAME;
        }
      } else {
        if (ordinal != null) {
          getWriter().key(ordinal.toString());
          fieldName = ordinal.toString();
        } else if (name != null) {
          getWriter().key(name);
          fieldName = name;
        } else {
          getWriter().key(BLANK_FIELD_NAME);
          fieldName = BLANK_FIELD_NAME;
        }
      }
    } catch (JSONException e) {
      wrapException("start of field", e);
    }
    return fieldName;
  }

  private String getFieldNameByOrdinal(final Integer ordinal, final int taxonomyId) {
    String result = ordinal.toString();
    FudgeTaxonomy taxonomy = getTaxonomy(taxonomyId);
    if (taxonomy != null) {
      String fieldName = taxonomy.getFieldName(ordinal);
      if (fieldName != null) {
        result = fieldName;
      }
    }
    return result;
  }
 
  private List<FudgeField> getFieldList(final Map<String, List<FudgeField>> fieldName2Fields, final String fieldName) {
    List<FudgeField> fields = fieldName2Fields.get(fieldName);
    if (fields == null) {
      fields = Lists.newArrayList();
      fieldName2Fields.put(fieldName, fields);
    }
    return fields;
  }

  private void envelopeComplete() {
    fudgeEnvelopeEnd();
  }
 
  /**
   * Ends the JSON object.
   */
  private void fudgeEnvelopeEnd() {
    try {
      getWriter().endObject();
      clearWriter();
    } catch (JSONException e) {
      wrapException("end of message", e);
    }
  }

  private void writeEnvelopeHeader(int processingDirectives, int version, int messageSize, int taxonomyId) {
    fudgeEnvelopeStart(processingDirectives, version, taxonomyId);
  }

  /**
   * Writes a message envelope using the default taxonomy.
   *
   * @param envelope message envelope to write
   * @throws NullPointerException if the default taxonomy has not been specified
   */
  public void writeMessageEnvelope(final FudgeMsgEnvelope envelope) {
    writeMessageEnvelope(envelope, DEFAULT_TAXONOMY_ID);
  }

  //-------------------------------------------------------------------------
  /**
   * Wraps a JSON exception (which may in turn wrap {@link IOException} into
   * either a {@link FudgeRuntimeException} or {@link FudgeRuntimeIOException}.
   *
   * @param message message describing the current operation
   * @param e the originating exception
   */
  private void wrapException(String message, final JSONException e) {
    message = "Error writing " + message + " to JSON";
    if (e.getCause() instanceof IOException) {
      throw new FudgeRuntimeIOException(message, (IOException) e.getCause());
    } else {
      throw new FudgeRuntimeException(message, e);
    }
  }

  /**
   * Flushes the underlying {@link Writer}.
   */
  @Override
  public void flush() {
    if (getUnderlying() != null) {
      try {
        getUnderlying().flush();
      } catch (IOException e) {
        throw new FudgeRuntimeIOException(e);
      }
    }
  }
 
  /**
   * Flushes and closes the underlying {@link Writer}.
   */
  @Override
  public void close() {
    flush();
    if (getUnderlying() != null) {
      try {
        getUnderlying().close();
      } catch (IOException ex) {
        throw new FudgeRuntimeIOException(ex);
      }
    }
  }
 
  /**
   * Begins a JSON object with the processing directives, schema and taxonomy.
   * @param taxonomyId
   */
  private void fudgeEnvelopeStart(final int processingDirectives, final int schemaVersion, int taxonomyId) {
    try {
      getWriter().object();
      if ((processingDirectives != 0) && (getSettings().getProcessingDirectivesField() != null)) {
        getWriter().key(getSettings().getProcessingDirectivesField()).value(processingDirectives);
      }
      if ((schemaVersion != 0) && (getSettings().getSchemaVersionField() != null)) {
        getWriter().key(getSettings().getSchemaVersionField()).value(schemaVersion);
      }
      if ((taxonomyId != DEFAULT_TAXONOMY_ID) && (getSettings().getTaxonomyField() != null)) {
        getWriter().key(getSettings().getTaxonomyField()).value(taxonomyId);
      }
    } catch (JSONException e) {
      wrapException("start of message", e);
    }
  }
 
}
TOP

Related Classes of com.opengamma.web.json.FudgeMsgJSONWriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.