package net.contrapunctus.rngzip.io; import java.io.Closeable; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.GZIPOutputStream; import net.contrapunctus.rngzip.util.BaliAutomaton; import net.contrapunctus.rngzip.util.BitOutputStream; import net.contrapunctus.rngzip.util.ContextualOutputStream; import net.contrapunctus.rngzip.util.ErrorReporter; import net.contrapunctus.rngzip.util.MultiplexOutputStream; import net.contrapunctus.rngzip.util.OutputStreamFilter; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; /** * This implements a compressed XML output interface by multiplexing * the tree structure and data stream(s) onto a single underlying * output stream. The embedded streams may optionally be compressed * using gzip. * * <p class='license'>This is free software; you may modify and/or * redistribute it under the terms of the GNU General Public License, * but it comes with <b>absolutely no warranty.</b> * * @author Christopher League */ public class RNGZOutputStream implements RNGZOutputInterface { private MultiplexOutputStream mux; private RNGZSettings settings; private BitOutputStream bits; private ContextualOutputStream data; private final boolean STATS = false; private final PrintStream dbg = System.err; private HashMap<String, Integer> tallies; /** * Construct an output stream for compressed XML data, which writes * (as a multiplex stream) to ‘out’. * @throws IllegalArgumentException if ‘out’ is null. * @throws IOException if there is trouble writing to ‘out’. */ public RNGZOutputStream(OutputStream out, RNGZSettings settings, BaliAutomaton au) throws IOException { this.settings = settings; mux = new MultiplexOutputStream(out, settings.magic()); settings.writeTo(mux, 1); bits = settings.newBitOutput(mux, 0); data = settings.newDataOutput(mux, 2); if (au != null) { data.writeUTF(null, au.getURL().toString()); data.writeLong(null, au.checksum()); } else { data.writeUTF(null, ""); } if(STATS) { tallies = new HashMap<String, Integer>(); } } private final void check() { if(mux == null) { throw new IllegalStateException("stream already closed"); } } public ChoiceEncoder makeChoiceEncoder(int limit, Object id) { return settings.makeChoiceCoder(limit, id); } /** * @throws IllegalStateException if the stream is already closed. */ public void writeChoice(ChoiceEncoder enc, int choice) throws IOException { check(); enc.encode(choice, bits); } /** * @throws IllegalStateException if the stream is already closed. */ public void writeContent(List<String> path, String s) throws IOException { check(); if(STATS) { String elt = path.get(path.size()-1); assert elt.intern() == elt; tally(elt); } data.writeUTF(path, s); } /** * @throws IllegalStateException if the stream is already closed. */ public void writeContent(List<String> path, char[] buf, int off, int len) throws IOException { writeContent(path, new String(buf, off, len)); } /** * Flushes the character data stream and underlying multiplex * stream. It’s never necessary to call this, as it happens * automatically on close. * @throws IOException if there is a problem on the underlying * stream. * @throws IllegalStateException if the stream is already closed. */ public void flush() throws IOException { check(); data.flush(); mux.flush(); } /** * Closes the stream; after this, the object becomes useless. * @throws IllegalStateException if the stream is already closed. */ public void close() throws IOException { check(); mux.close(); if(STATS) { for(Map.Entry<String, Integer> e : tallies.entrySet()) { dbg.printf("%20s %5d%n", e.getKey(), e.getValue()); } } mux = null; bits = null; data = null; tallies = null; } private void tally(String elt) { Integer i = tallies.get(elt); if(i == null) { i = 0; } tallies.put(elt, i+1); } }