001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.doxia.sink.impl;
020
021import java.io.ByteArrayOutputStream;
022import java.io.File;
023import java.io.FileOutputStream;
024import java.io.IOException;
025import java.io.OutputStream;
026import java.util.ArrayList;
027import java.util.List;
028
029import org.apache.maven.doxia.sink.Sink;
030import org.apache.maven.doxia.sink.SinkFactory;
031
032/**
033 * The RandomAccessSink provides the ability to create a {@link Sink} with hooks.
034 * A page can be prepared by first creating its structure and specifying the positions of these hooks.
035 * After specifying the structure, the page can be filled with content from one or more models.
036 * These hooks can prevent you to have to loop over the model multiple times to build the page as desired.
037 * @deprecated Use {@link BufferingSinkProxyFactory} instead which buffers on the (higher) Sink API level
038 * which usually is less memory intense than buffering the output stream which is done by this class.
039 * Also it doesn't require dynamically creating new sinks leveraging a {@link SinkFactory}.
040 *
041 * @author Robert Scholte
042 * @since 1.3
043 * @see BufferingSinkProxyFactory
044 */
045@Deprecated
046public class RandomAccessSink extends SinkWrapper {
047    private SinkFactory sinkFactory;
048
049    private String encoding;
050
051    private OutputStream coreOutputStream;
052
053    private Sink coreSink;
054
055    private List<Sink> sinks = new ArrayList<>();
056
057    private List<ByteArrayOutputStream> outputStreams = new ArrayList<>();
058
059    private Sink currentSink;
060
061    /**
062     * <p>Constructor for RandomAccessSink.</p>
063     *
064     * @param sinkFactory a {@link org.apache.maven.doxia.sink.SinkFactory} object.
065     * @param stream a {@link java.io.OutputStream} object.
066     * @throws java.io.IOException if any.
067     */
068    public RandomAccessSink(SinkFactory sinkFactory, OutputStream stream) throws IOException {
069        super(sinkFactory.createSink(stream));
070        this.sinkFactory = sinkFactory;
071        this.coreOutputStream = stream;
072        this.coreSink = getWrappedSink();
073    }
074
075    /**
076     * <p>Constructor for RandomAccessSink.</p>
077     *
078     * @param sinkFactory a {@link org.apache.maven.doxia.sink.SinkFactory} object.
079     * @param stream a {@link java.io.OutputStream} object.
080     * @param encoding a {@link java.lang.String} object.
081     * @throws java.io.IOException if any.
082     */
083    public RandomAccessSink(SinkFactory sinkFactory, OutputStream stream, String encoding) throws IOException {
084        super(sinkFactory.createSink(stream, encoding));
085        this.sinkFactory = sinkFactory;
086        this.coreOutputStream = stream;
087        this.encoding = encoding;
088        this.coreSink = getWrappedSink();
089    }
090
091    /**
092     * <p>Constructor for RandomAccessSink.</p>
093     *
094     * @param sinkFactory a {@link org.apache.maven.doxia.sink.SinkFactory} object.
095     * @param outputDirectory a {@link java.io.File} object.
096     * @param outputName a {@link java.lang.String} object.
097     * @throws java.io.IOException if any.
098     */
099    public RandomAccessSink(SinkFactory sinkFactory, File outputDirectory, String outputName) throws IOException {
100        super(null);
101        this.sinkFactory = sinkFactory;
102        this.coreOutputStream = new FileOutputStream(new File(outputDirectory, outputName));
103        this.currentSink = sinkFactory.createSink(coreOutputStream);
104        this.coreSink = this.currentSink;
105        setWrappedSink(currentSink);
106    }
107
108    /**
109     * <p>Constructor for RandomAccessSink.</p>
110     *
111     * @param sinkFactory a {@link org.apache.maven.doxia.sink.SinkFactory} object.
112     * @param outputDirectory a {@link java.io.File} object.
113     * @param outputName a {@link java.lang.String} object.
114     * @param encoding a {@link java.lang.String} object.
115     * @throws java.io.IOException if any.
116     */
117    public RandomAccessSink(SinkFactory sinkFactory, File outputDirectory, String outputName, String encoding)
118            throws IOException {
119        super(null);
120        this.sinkFactory = sinkFactory;
121        this.coreOutputStream = new FileOutputStream(new File(outputDirectory, outputName));
122        this.encoding = encoding;
123        this.currentSink = sinkFactory.createSink(coreOutputStream, encoding);
124        this.coreSink = this.currentSink;
125        setWrappedSink(currentSink);
126    }
127
128    /**
129     * By calling this method a sink reference is added at the current position. You can write to both the new sink
130     * reference and the original sink. After flushing all sinks will be flushed in the right order.
131     *
132     * @return a subsink reference you can write to
133     */
134    public Sink addSinkHook() {
135        Sink subSink = null;
136        try {
137            ByteArrayOutputStream subOut = new ByteArrayOutputStream();
138            ByteArrayOutputStream newOut = new ByteArrayOutputStream();
139
140            outputStreams.add(subOut);
141            outputStreams.add(newOut);
142
143            if (encoding != null) {
144                subSink = sinkFactory.createSink(subOut, encoding);
145                currentSink = sinkFactory.createSink(newOut, encoding);
146            } else {
147                subSink = sinkFactory.createSink(subOut);
148                currentSink = sinkFactory.createSink(newOut);
149            }
150            sinks.add(subSink);
151            sinks.add(currentSink);
152            setWrappedSink(currentSink);
153        } catch (IOException e) {
154            // IOException can only be caused by our own ByteArrayOutputStream
155        }
156        return subSink;
157    }
158
159    /**
160     * Close all sinks
161     */
162    public void close() {
163        for (Sink sink : sinks) {
164            // sink is responsible for closing it's stream
165            sink.close();
166        }
167        coreSink.close();
168    }
169
170    /**
171     * Flush all sinks
172     */
173    public void flush() {
174        for (int i = 0; i < sinks.size(); i++) {
175            // first flush to get complete buffer
176            // sink is responsible for flushing it's stream
177            Sink sink = sinks.get(i);
178            sink.flush();
179
180            ByteArrayOutputStream stream = outputStreams.get(i);
181            try {
182                coreOutputStream.write(stream.toByteArray());
183            } catch (IOException e) {
184                // @todo
185            }
186        }
187        coreSink.flush();
188    }
189}