All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.cloud.objectstorage.thirdparty.ion.util.Printer Maven / Gradle / Ivy

Go to download

A single bundled dependency that includes all service and dependent JARs with third-party libraries relocated to different namespaces.

There is a newer version: 2.13.4
Show newest version
/*
 * Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * or in the "license" file accompanying this file. This file 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 software.amazon.ion.util;

import static software.amazon.ion.SystemSymbols.IMPORTS;
import static software.amazon.ion.SystemSymbols.ION_1_0_SID;
import static software.amazon.ion.SystemSymbols.ION_SYMBOL_TABLE;
import static software.amazon.ion.SystemSymbols.SYMBOLS;

import software.amazon.ion.Decimal;
import software.amazon.ion.IonBlob;
import software.amazon.ion.IonBool;
import software.amazon.ion.IonClob;
import software.amazon.ion.IonDatagram;
import software.amazon.ion.IonDecimal;
import software.amazon.ion.IonFloat;
import software.amazon.ion.IonInt;
import software.amazon.ion.IonList;
import software.amazon.ion.IonNull;
import software.amazon.ion.IonReader;
import software.amazon.ion.IonSequence;
import software.amazon.ion.IonSexp;
import software.amazon.ion.IonString;
import software.amazon.ion.IonStruct;
import software.amazon.ion.IonSymbol;
import software.amazon.ion.IonTimestamp;
import software.amazon.ion.IonType;
import software.amazon.ion.IonValue;
import software.amazon.ion.IonWriter;
import software.amazon.ion.SymbolTable;
import software.amazon.ion.SymbolToken;
import software.amazon.ion.Timestamp;
import software.amazon.ion.impl._Private_IonSymbol;
import software.amazon.ion.impl._Private_IonSystem;
import software.amazon.ion.impl._Private_IonTextWriterBuilder;
import software.amazon.ion.impl._Private_IonValue;
import software.amazon.ion.impl._Private_IonValue.SymbolTableProvider;
import software.amazon.ion.system.IonTextWriterBuilder;
import software.amazon.ion.system.IonTextWriterBuilder.LstMinimizing;
import software.amazon.ion.system.IonWriterBuilder.IvmMinimizing;
import software.amazon.ion.util.IonTextUtils.SymbolVariant;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.Iterator;


/**
 * Renders {@link IonValue}s to text.
 * 

* By default, output is in a compact format with minimal whitespace. * For example: *

 *    annot::{f1:["hello","goodbye"],'another field':long::0}
 *
* The format can be tuned through various properties on the Printer instance, * as well as through the {@link Printer.Options} structure. *

* Instances of this class are safe for use by multiple threads. *

* While printers are inexpensive to create, their configuration facilities * make them useful as shared resources. Changes to configuration settings * (e.g., {@link #setJsonMode()}) do not affect concurrently-running * calls to {@link #print}. * * @see IonWriter * @see IonTextWriterBuilder */ public class Printer { public class Options implements Cloneable { public boolean blobAsString; public boolean clobAsString; public boolean datagramAsList; public boolean decimalAsFloat; public boolean sexpAsList; public boolean skipAnnotations; public boolean skipSystemValues; public boolean simplifySystemValues; public boolean stringAsJson; public boolean symbolAsString; public boolean timestampAsString; public boolean timestampAsMillis; public boolean untypedNulls; @Override public Options clone() { try { return (Options) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); } } } protected Options myOptions = new Options(); public Printer() { myOptions = new Options(); } public Printer(Options options) { myOptions = options.clone(); } //========================================================================= // Options /* Potential printing options: * * - Render all times with a specific offset (what about unknowns?) * - Render all times with long offset (no Z) * - control over decimal-point placement in float and decimal. * (render 12.00 or 1200d-2) */ /** * Indicates whether this printer skips (i.e., doesn't print) * system IDs and local symbol tables. * By default, this property is false. */ public synchronized boolean getSkipSystemValues() { return myOptions.skipSystemValues; } /** * Sets whether this printer skips (i.e., doesn't print) * system IDs and local symbol tables. * By default, this property is false. */ public synchronized void setSkipSystemValues(boolean skip) { myOptions.skipSystemValues = skip; } /** * Indicates whether this printer skips (i.e., doesn't print) * annotations. * By default, this property is false. */ public synchronized boolean getSkipAnnotations() { return myOptions.skipAnnotations; } /** * Sets whether this printer skips (i.e., doesn't print) * annotations. * By default, this property is false. */ public synchronized void setSkipAnnotations(boolean skip) { myOptions.skipAnnotations = skip; } /** * Indicates whether this printer renders blobs as Base64 strings. * By default, this is false. */ public synchronized boolean getPrintBlobAsString() { return myOptions.blobAsString; } /** * Sets whether this printer renders blobs as Base64 strings. * By default, this is false. */ public synchronized void setPrintBlobAsString(boolean blobAsString) { myOptions.blobAsString = blobAsString; } /** * Indicates whether this printer renders clobs as ASCII strings. * By default, this is false. */ public synchronized boolean getPrintClobAsString() { return myOptions.clobAsString; } /** * Sets whether this printer renders clobs as ASCII strings. * By default, this is false. */ public synchronized void setPrintClobAsString(boolean clobAsString) { myOptions.clobAsString = clobAsString; } /** * Indicates whether this printer renders datagrams as lists. * By default, this property is false. */ public synchronized boolean getPrintDatagramAsList() { return myOptions.datagramAsList; } /** * Sets whether this printer renders datagrams as lists. * By default, this property is false. */ public synchronized void setPrintDatagramAsList(boolean datagramAsList) { myOptions.datagramAsList = datagramAsList; } /** * Indicates whether this printer renders decimals as floats, thus using 'e' * notation for all real values. * By default, this is false. */ public synchronized boolean getPrintDecimalAsFloat() { return myOptions.decimalAsFloat; } /** * Sets whether this printer renders decimals as floats, thus using 'e' * notation for all real values. * By default, this is false. */ public synchronized void setPrintDecimalAsFloat(boolean decimalAsFloat) { myOptions.decimalAsFloat = decimalAsFloat; } /** * Indicates whether this printer renders sexps as lists. * By default, this is false. */ public synchronized boolean getPrintSexpAsList() { return myOptions.sexpAsList; } /** * Sets whether this printer renders sexps as lists. * By default, this is false. */ public synchronized void setPrintSexpAsList(boolean sexpAsList) { myOptions.sexpAsList = sexpAsList; } /** * Indicates whether this printer renders strings using JSON escapes. * By default, this is false. */ public synchronized boolean getPrintStringAsJson() { return myOptions.stringAsJson; } /** * Sets whether this printer renders strings using JSON escapes. * By default, this is false. */ public synchronized void setPrintStringAsJson(boolean stringAsJson) { myOptions.stringAsJson = stringAsJson; } /** * Indicates whether this printer renders symbols as strings. * By default, this is false. */ public synchronized boolean getPrintSymbolAsString() { return myOptions.symbolAsString; } /** * Sets whether this printer renders symbols as strings. * By default, this is false. */ public synchronized void setPrintSymbolAsString(boolean symbolAsString) { myOptions.symbolAsString = symbolAsString; } /** * Indicates whether this printer renders timestamps as millisecond values. * By default, this is false. */ public synchronized boolean getPrintTimestampAsMillis() { return myOptions.timestampAsMillis; } /** * Sets whether this printer renders timestamps as millisecond values. * By default, this is false. */ public synchronized void setPrintTimestampAsMillis(boolean timestampAsMillis) { myOptions.timestampAsMillis = timestampAsMillis; } /** * Indicates whether this printer renders timestamps as strings. * By default, this is false. */ public synchronized boolean getPrintTimestampAsString() { return myOptions.timestampAsString; } /** * Sets whether this printer renders timestamps as strings. * By default, this is false. */ public synchronized void setPrintTimestampAsString(boolean timestampAsString) { myOptions.timestampAsString = timestampAsString; } /** * Indicates whether this printer renders all null values as {@code null} * (i.e., the same as an {@link IonNull}). * By default, this is false. */ public synchronized boolean getPrintUntypedNulls() { return myOptions.untypedNulls; } /** * Sets whether this printer renders all null values as {@code null} * (i.e., the same as an {@link IonNull}). * By default, this is false. */ public synchronized void setPrintUntypedNulls(boolean untypedNulls) { myOptions.untypedNulls = untypedNulls; } /** * Configures this printer's options to render legal JSON text. * The following options are modified so that: *

    *
  • {@link Options#blobAsString} is {@code true}
  • *
  • {@link Options#clobAsString} is {@code true}
  • *
  • {@link Options#datagramAsList} is {@code true}
  • *
  • {@link Options#decimalAsFloat} is {@code true}
  • *
  • {@link Options#sexpAsList} is {@code true}
  • *
  • {@link Options#skipAnnotations} is {@code true}
  • *
  • {@link Options#skipSystemValues} is {@code true}
  • *
  • {@link Options#stringAsJson} is {@code true}
  • *
  • {@link Options#symbolAsString} is {@code true}
  • *
  • {@link Options#timestampAsString} is {@code false}
  • *
  • {@link Options#timestampAsMillis} is {@code true}
  • *
  • {@link Options#untypedNulls} is {@code true}
  • *
* All other options are left as is. */ public synchronized void setJsonMode() { myOptions.blobAsString = true; myOptions.clobAsString = true; myOptions.datagramAsList = true; myOptions.decimalAsFloat = true; myOptions.sexpAsList = true; myOptions.skipAnnotations = true; myOptions.skipSystemValues = true; myOptions.stringAsJson = true; myOptions.symbolAsString = true; myOptions.timestampAsString = false; myOptions.timestampAsMillis = true; myOptions.untypedNulls = true; } //========================================================================= // Print methods public void print(IonValue value, Appendable out) throws IOException { // Copy the options so visitor won't see changes made while printing. Options options; synchronized (this) // So we don't clone in the midst of changes { options = myOptions.clone(); } if (true) { _print(value, makeVisitor(options, out)); } else { // Bridge to the configurable text writer. This is here for // testing purposes. It *almost* works except for printing // datagram as list. boolean dg = value instanceof IonDatagram; _Private_IonTextWriterBuilder o = _Private_IonTextWriterBuilder.standard(); o.setCharset(IonTextWriterBuilder.ASCII); if (dg) { if (options.skipSystemValues) { o.withMinimalSystemData(); } else if (options.simplifySystemValues) { o.setIvmMinimizing(IvmMinimizing.DISTANT); o.setLstMinimizing(LstMinimizing.LOCALS); } } o._blob_as_string = options.blobAsString; o._clob_as_string = options.clobAsString; o._decimal_as_float = options.decimalAsFloat; // TODO datagram as list o._sexp_as_list = options.sexpAsList; o._skip_annotations = options.skipAnnotations; o._string_as_json = options.stringAsJson; o._symbol_as_string = options.symbolAsString; o._timestamp_as_millis = options.timestampAsMillis; o._timestamp_as_string = options.timestampAsString; o._untyped_nulls = options.untypedNulls; IonWriter writer = o.build(out); // TODO doesn't work for datagram since it skips system values // value.writeTo(writer); _Private_IonSystem system = (_Private_IonSystem) value.getSystem(); IonReader reader = system.newSystemReader(value); writer.writeValues(reader); writer.finish(); } } private void _print(IonValue value, PrinterVisitor pv) throws IOException { try { if (! (value instanceof IonDatagram)) { pv.setSymbolTableProvider(new BasicSymbolTableProvider(value.getSymbolTable())); } value.accept(pv); } catch (IOException e) { throw e; } catch (RuntimeException e) { throw e; } catch (Exception e) { // Shouldn't happen. throw new RuntimeException(e); } } /** * Subclasses can override this if they wish to construct a specialization * of the {@link PrinterVisitor}. * * @param options is a fresh copy of the Printer's options instance, * not null. * @param out is not null. * @return the visitor to invoke for printing. */ protected PrinterVisitor makeVisitor(Options options, Appendable out) { return new PrinterVisitor(options, out); } private static class BasicSymbolTableProvider implements SymbolTableProvider { private final SymbolTable symbolTable; public BasicSymbolTableProvider(SymbolTable symbolTable) { this.symbolTable = symbolTable; } public SymbolTable getSymbolTable() { return symbolTable; } } //========================================================================= // Print methods public static class PrinterVisitor extends AbstractValueVisitor { final protected Options myOptions; final protected Appendable myOut; /** * Should we quote operators at the current level of the hierarchy? * As we recurse down into containers, this value is pushed on the * stack by {@link #writeChild(IonValue, boolean)}. */ private boolean myQuoteOperators = true; private SymbolTableProvider mySymbolTableProvider = null; //--------------------------------------------------------------------- public PrinterVisitor(Options options, Appendable out) { myOptions = options; myOut = out; } void setSymbolTableProvider(SymbolTableProvider symbolTableProvider) { mySymbolTableProvider = symbolTableProvider; } /** * Recurse down into a container, we push the current value of * {@link #myQuoteOperators} onto the stack and replace it with * the given value. * * @param value * @param quoteOperators replaces the current value of * {@link #myQuoteOperators} during the recursive visitation. * @throws Exception propagated from visitation of value. * @throws NullPointerException if value is null. */ protected void writeChild(IonValue value, boolean quoteOperators) throws Exception { boolean oldQuoteOperators = myQuoteOperators; myQuoteOperators = quoteOperators; value.accept(this); myQuoteOperators = oldQuoteOperators; } public void writeAnnotations(IonValue value) throws IOException { if (! myOptions.skipAnnotations) { SymbolToken[] anns = ((_Private_IonValue)value).getTypeAnnotationSymbols(mySymbolTableProvider); if (anns != null) { for (SymbolToken ann : anns) { String text = ann.getText(); if (text == null) { myOut.append('$'); myOut.append(Integer.toString(ann.getSid())); } else { IonTextUtils.printSymbol(myOut, text); } myOut.append("::"); } } } } public void writeNull(String type) throws IOException { if (myOptions.untypedNulls) { myOut.append("null"); } else { myOut.append("null."); myOut.append(type); } } public void writeSequenceContent(IonSequence value, boolean quoteOperators, char open, char separator, char close) throws IOException, Exception { myOut.append(open); boolean hitOne = false; for (IonValue child : value) { if (hitOne) { myOut.append(separator); } hitOne = true; writeChild(child, quoteOperators); } myOut.append(close); } public void writeSymbolToken(SymbolToken sym) throws IOException { String text = sym.getText(); if (text != null) { writeSymbol(text); } else { int sid = sym.getSid(); if (sid < 0) { throw new IllegalArgumentException("Bad SID " + sid); } text = "$" + sym.getSid(); if (myOptions.symbolAsString) { writeString(text); } else { myOut.append(text); // SID literal is never quoted } } } public void writeSymbol(String text) throws IOException { if (myOptions.symbolAsString) { writeString(text); } else { SymbolVariant variant = IonTextUtils.symbolVariant(text); switch (variant) { case IDENTIFIER: myOut.append(text); break; case OPERATOR: if (! myQuoteOperators) { myOut.append(text); break; } // else fall through... case QUOTED: IonTextUtils.printQuotedSymbol(myOut, text); break; } } } /** * @param text may be null */ public void writeString(String text) throws IOException { if (myOptions.stringAsJson) { IonTextUtils.printJsonString(myOut, text); } else { IonTextUtils.printString(myOut, text); } } //--------------------------------------------------------------------- // AbstractValueVisitor overrides @Override protected void defaultVisit(IonValue value) { String message = "cannot print " + value.getClass().getName(); throw new UnsupportedOperationException(message); } @Override public void visit(IonBlob value) throws IOException { writeAnnotations(value); if (value.isNullValue()) { writeNull("blob"); } else { myOut.append(myOptions.blobAsString ? "\"" : "{{"); value.printBase64(myOut); myOut.append(myOptions.blobAsString ? "\"" : "}}"); } } @Override public void visit(IonBool value) throws IOException { writeAnnotations(value); if (value.isNullValue()) { writeNull("bool"); } else { myOut.append(value.booleanValue() ? "true" : "false"); } } @Override public void visit(IonClob value) throws IOException { writeAnnotations(value); if (value.isNullValue()) { writeNull("clob"); } else { if (! myOptions.clobAsString) { myOut.append("{{"); } myOut.append('"'); InputStream byteStream = value.newInputStream(); try { int c; // if-statement hoisted above loop for efficiency if (myOptions.stringAsJson) { while ((c = byteStream.read()) != -1) { IonTextUtils.printJsonCodePoint(myOut, c); } } else { while ((c = byteStream.read()) != -1) { IonTextUtils.printStringCodePoint(myOut, c); } } } finally { byteStream.close(); } myOut.append('"'); if (! myOptions.clobAsString) { myOut.append("}}"); } } } @Override public void visit(IonDatagram value) throws IOException, Exception { Iterator i = (myOptions.skipSystemValues ? value.iterator() : value.systemIterator()); final boolean asList = myOptions.datagramAsList; if (asList) { myOut.append('['); } boolean hitOne = false; // If we're skipping system values at the iterator level, // we don't need to bother trying to simplify them. final boolean simplify_system_values = myOptions.simplifySystemValues && ! myOptions.skipSystemValues; SymbolTable previous_symbols = null; while (i.hasNext()) { IonValue child = i.next(); SymbolTable childSymbolTable = child.getSymbolTable(); mySymbolTableProvider = new BasicSymbolTableProvider(childSymbolTable); //children of datagrams are top-level values if (simplify_system_values) { child = simplify(child, previous_symbols); previous_symbols = childSymbolTable; } if (child != null) { if (hitOne) { myOut.append(asList ? ',' : ' '); } writeChild(child, true); hitOne = true; // we've only "hit one" if we wrote it } } if (asList) { myOut.append(']'); } } private final IonValue simplify(IonValue child, SymbolTable previous_symbols) { IonType t = child.getType(); switch (t) { case STRUCT: if (child.hasTypeAnnotation(ION_SYMBOL_TABLE)) { if (symbol_table_struct_has_imports(child)) { return ((IonStruct)child).cloneAndRemove(SYMBOLS); } return null; } // fall through to default (print the value) break; case SYMBOL: if (((IonSymbol)child).getSymbolId() == ION_1_0_SID) { if (previous_symbols != null && previous_symbols.isSystemTable()) { return null; } // fall through to default (print the value) } // fall through to default (print the value) break; default: break; } return child; } static final private boolean symbol_table_struct_has_imports(IonValue child) { IonStruct struct = (IonStruct)child; IonValue imports = struct.get(IMPORTS); if (imports instanceof IonList) { return ((IonList)imports).size() != 0; } return false; } @Override public void visit(IonDecimal value) throws IOException { writeAnnotations(value); if (value.isNullValue()) { writeNull("decimal"); } else { Decimal decimal = value.decimalValue(); BigInteger unscaled = decimal.unscaledValue(); int signum = decimal.signum(); if (signum < 0) { myOut.append('-'); unscaled = unscaled.negate(); } else if (signum == 0 && decimal.isNegativeZero()) { // for the various forms of negative zero we have to // write the sign ourselves, since neither BigInteger // nor BigDecimal recognize negative zero, but Ion does. myOut.append('-'); } final String unscaledText = unscaled.toString(); final int significantDigits = unscaledText.length(); final int scale = decimal.scale(); final int exponent = -scale; if (myOptions.decimalAsFloat) { myOut.append(unscaledText); myOut.append('e'); myOut.append(Integer.toString(exponent)); } else if (exponent == 0) { myOut.append(unscaledText); myOut.append('.'); } else if (0 < scale) { int wholeDigits; int remainingScale; if (significantDigits > scale) { wholeDigits = significantDigits - scale; remainingScale = 0; } else { wholeDigits = 1; remainingScale = scale - significantDigits + 1; } myOut.append(unscaledText, 0, wholeDigits); if (wholeDigits < significantDigits) { myOut.append('.'); myOut.append(unscaledText, wholeDigits, significantDigits); } if (remainingScale != 0) { myOut.append("d-"); myOut.append(Integer.toString(remainingScale)); } } else // (exponent > 0) { // We cannot move the decimal point to the right, adding // rightmost zeros, because that would alter the precision. myOut.append(unscaledText); myOut.append('d'); myOut.append(Integer.toString(exponent)); } } } @Override public void visit(IonFloat value) throws IOException { writeAnnotations(value); if (value.isNullValue()) { writeNull("float"); } else { double real = value.doubleValue(); IonTextUtils.printFloat(myOut, real); } } @Override public void visit(IonInt value) throws IOException { writeAnnotations(value); if (value.isNullValue()) { writeNull("int"); } else { myOut.append(value.bigIntegerValue().toString(10)); } } @Override public void visit(IonList value) throws IOException, Exception { writeAnnotations(value); if (value.isNullValue()) { writeNull("list"); } else { writeSequenceContent(value, true, '[', ',', ']'); } } @Override public void visit(IonNull value) throws IOException { writeAnnotations(value); myOut.append("null"); } @Override public void visit(IonSexp value) throws IOException, Exception { writeAnnotations(value); if (value.isNullValue()) { writeNull("sexp"); } else if (myOptions.sexpAsList) { writeSequenceContent(value, true, '[', ',', ']'); } else { writeSequenceContent(value, false, '(', ' ', ')'); } } @Override public void visit(IonString value) throws IOException { writeAnnotations(value); if (value.isNullValue()) { writeNull("string"); } else { writeString(value.stringValue()); } } @Override public void visit(IonStruct value) throws IOException, Exception { writeAnnotations(value); if (value.isNullValue()) { writeNull("struct"); } else { myOut.append('{'); boolean hitOne = false; for (IonValue child : value) { if (hitOne) { myOut.append(','); } hitOne = true; SymbolToken sym = ((_Private_IonValue)child).getFieldNameSymbol(mySymbolTableProvider); writeSymbolToken(sym); myOut.append(':'); writeChild(child, true); } myOut.append('}'); } } @Override public void visit(IonSymbol value) throws IOException { writeAnnotations(value); SymbolToken is = ((_Private_IonSymbol)value).symbolValue(mySymbolTableProvider); if (is == null) { writeNull("symbol"); } else { writeSymbolToken(is); } } @Override public void visit(IonTimestamp value) throws IOException { writeAnnotations(value); if (value.isNullValue()) { writeNull("timestamp"); } else if (myOptions.timestampAsMillis) { myOut.append(Long.toString(value.getMillis())); } else { Timestamp ts = value.timestampValue(); if (myOptions.timestampAsString) { myOut.append('"'); ts.print(myOut); myOut.append('"'); } else { ts.print(myOut); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy