001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.serializer;
018
019import static org.apache.juneau.commons.utils.IoUtils.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022
023import java.io.*;
024import java.nio.charset.*;
025
026import org.apache.juneau.commons.io.*;
027import org.apache.juneau.commons.utils.*;
028
029/**
030 * A wrapper around an object that a serializer sends its output to.
031 *
032 * <p>
033 * For character-based serializers, the output object can be any of the following:
034 * <ul>
035 *    <li>{@link Writer}
036 *    <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream.
037 *    <li>{@link File} - Output will be written as system-default encoded stream.
038 *    <li>{@link StringBuilder}
039 * </ul>
040 *
041 * <p>
042 * For stream-based serializers, the output object can be any of the following:
043 * <ul>
044 *    <li>{@link OutputStream}
045 *    <li>{@link File}
046 * </ul>
047 *
048 * <h5 class='section'>See Also:</h5><ul>
049 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
050 * </ul>
051 */
052public class SerializerPipe implements Closeable {
053
054   private final Object output;
055   private final boolean autoClose;
056
057   private OutputStream outputStream;
058   private Writer writer;
059   private Charset charset;
060
061   /**
062    * Stream-based constructor.
063    *
064    * @param output The object to pipe the serializer output to.
065    */
066   SerializerPipe(Object output) {
067      this.output = output;
068      this.autoClose = false;
069      this.charset = null;
070   }
071
072   /**
073    * Writer-based constructor.
074    *
075    * @param output The object to pipe the serializer output to.
076    */
077   SerializerPipe(Object output, Charset streamCharset, Charset fileCharset) {
078      boolean isFile = (output instanceof File);
079      this.output = output;
080      this.autoClose = isFile;
081      Charset cs = isFile ? fileCharset : streamCharset;
082      if (cs == null)
083         cs = isFile ? Charset.defaultCharset() : UTF8;
084      this.charset = cs;
085   }
086
087   /**
088    * Closes the output pipe.
089    */
090   @Override /* Overridden from Closeable */
091   public void close() {
092      try {
093         IoUtils.flush(writer, outputStream);
094         if (autoClose)
095            IoUtils.close(writer, outputStream);
096      } catch (IOException e) {
097         throw bex(e);
098      }
099   }
100
101   /**
102    * Wraps the specified output object inside an output stream.
103    *
104    * <p>
105    * Subclasses can override this method to implement their own specialized output streams.
106    *
107    * <p>
108    * This method can be used if the output object is any of the following class types:
109    * <ul>
110    *    <li>{@link OutputStream}
111    *    <li>{@link File}
112    * </ul>
113    *
114    * @return
115    *    The output object wrapped in an output stream.
116    *    Calling {@link OutputStream#close()} on the returned object simply flushes the response and does not close
117    *    the underlying stream.
118    * @throws IOException If object could not be converted to an output stream.
119    */
120   public OutputStream getOutputStream() throws IOException {
121      if (output == null)
122         throw ioex("Output cannot be null.");
123
124      if (output instanceof OutputStream output2)
125         outputStream = output2;
126      else if (output instanceof File output2)
127         outputStream = new BufferedOutputStream(new FileOutputStream(output2));
128      else
129         throw ioex("Cannot convert object of type {0} to an OutputStream.", cn(output));
130
131      return new NoCloseOutputStream(outputStream);
132   }
133
134   /**
135    * Returns the raw output object passed into this session.
136    *
137    * @return The raw output object passed into this session.
138    */
139   public Object getRawOutput() { return output; }
140
141   /**
142    * Wraps the specified output object inside a writer.
143    *
144    * <p>
145    * Subclasses can override this method to implement their own specialized writers.
146    *
147    * <p>
148    * This method can be used if the output object is any of the following class types:
149    * <ul>
150    *    <li>{@link Writer}
151    *    <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream.
152    *    <li>{@link File} - Output will be written as system-default encoded stream.
153    * </ul>
154    *
155    * @return
156    *    The output object wrapped in a writer.
157    *    Calling {@link Writer#close()} on the returned object simply flushes the response and does not close
158    *    the underlying writer.
159    * @throws SerializeException If object could not be converted to a writer.
160    */
161   public Writer getWriter() throws SerializeException {
162      if (output == null)
163         throw new SerializeException("Output cannot be null.");
164
165      try {
166         if (output instanceof Writer output2)
167            writer = output2;
168         else if (output instanceof OutputStream output2)
169            writer = new OutputStreamWriter(output2, charset);
170         else if (output instanceof File output2)
171            writer = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(output2)));
172         else if (output instanceof StringBuilder output2)
173            writer = new StringBuilderWriter(output2);
174         else
175            throw new SerializeException("Cannot convert object of type " + cn(output) + " to a Writer.");
176      } catch (FileNotFoundException e) {
177         throw castException(SerializeException.class, e);
178      }
179
180      return new NoCloseWriter(writer);
181   }
182
183   /**
184    * Overwrites the output stream in this pipe.
185    *
186    * <p>
187    * Used when wrapping the stream returned by {@link #getOutputStream()} so that the wrapped stream will be flushed
188    * when {@link #close()} is called.
189    *
190    * @param outputStream The wrapped stream.
191    */
192   public void setOutputStream(OutputStream outputStream) { this.outputStream = outputStream; }
193
194   /**
195    * Overwrites the writer in this pipe.
196    *
197    * <p>
198    * Used when wrapping the writer returned by {@link #getWriter()} so that the wrapped writer will be flushed
199    * and closed when {@link #close()} is called.
200    *
201    * @param writer The wrapped writer.
202    */
203   public void setWriter(Writer writer) { this.writer = writer; }
204}