001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.internal;
014
015import java.io.*;
016import java.util.*;
017
018/**
019 * Output stream that can send output to multiple output streams.
020 */
021public class TeeOutputStream extends OutputStream {
022   private OutputStream[] outputStreams = new OutputStream[0];
023   private Map<String,OutputStream> outputStreamMap;
024
025   /**
026    * Constructor.
027    *
028    * @param outputStreams The list of output streams.
029    */
030   public TeeOutputStream(OutputStream...outputStreams) {
031      this.outputStreams = outputStreams;
032   }
033
034   /**
035    * Constructor.
036    *
037    * @param outputStreams The list of output streams.
038    */
039   public TeeOutputStream(Collection<OutputStream> outputStreams) {
040      this.outputStreams = outputStreams.toArray(new OutputStream[outputStreams.size()]);
041   }
042
043   /**
044    * Adds an output stream to this tee output stream.
045    *
046    * @param os The output stream to add to this tee output stream.
047    * @param close
048    *    If <jk>false</jk>, then calling {@link #close()} on this stream will not filter to the specified output stream.
049    * @return This object (for method chaining).
050    */
051   public TeeOutputStream add(OutputStream os, boolean close) {
052      if (os == null)
053         return this;
054      if (! close)
055         os = new NoCloseOutputStream(os);
056      if (os == this)
057         throw new RuntimeException("Cannot add this output stream to itself.");
058      for (OutputStream os2 : outputStreams)
059         if (os2 == os)
060            throw new RuntimeException("Cannot add this output stream again.");
061      if (os instanceof TeeOutputStream) {
062         for (OutputStream os2 : ((TeeOutputStream)os).outputStreams)
063            add(os2, true);
064      } else {
065         outputStreams = ArrayUtils.append(outputStreams, os);
066      }
067      return this;
068   }
069
070   /**
071    * Returns the output stream identified through the <code>id</code> parameter passed in through the
072    * {@link #add(String, OutputStream, boolean)} method.
073    *
074    * @param id The ID associated with the output stream.
075    * @return The output stream, or <jk>null</jk> if no identifier was specified when the output stream was added.
076    */
077   public OutputStream getOutputStream(String id) {
078      if (outputStreamMap != null)
079         return outputStreamMap.get(id);
080      return null;
081   }
082
083   /**
084    * Same as {@link #add(OutputStream, boolean)} but associates the stream with an identifier so the stream can be
085    * retrieved through {@link #getOutputStream(String)}.
086    *
087    * @param id The ID to associate the output stream with.
088    * @param os The output stream to add.
089    * @param close Close the specified stream afterwards.
090    * @return This object (for method chaining).
091    */
092   public TeeOutputStream add(String id, OutputStream os, boolean close) {
093      if (id != null) {
094         if (outputStreamMap == null)
095            outputStreamMap = new TreeMap<>();
096         outputStreamMap.put(id, os);
097      }
098      return add(os, close);
099   }
100
101   /**
102    * Returns the number of inner streams in this tee stream.
103    *
104    * @return The number of streams in this tee stream.
105    */
106   public int size() {
107      return outputStreams.length;
108   }
109
110   @Override /* OutputStream */
111   public void write(int b) throws IOException {
112      for (OutputStream os : outputStreams)
113         os.write(b);
114   }
115
116   @Override /* OutputStream */
117   public void write(byte b[], int off, int len) throws IOException {
118      for (OutputStream os : outputStreams)
119         os.write(b, off, len);
120   }
121
122   @Override /* OutputStream */
123   public void flush() throws IOException {
124      for (OutputStream os : outputStreams)
125         os.flush();
126   }
127
128   @Override /* OutputStream */
129   public void close() throws IOException {
130      for (OutputStream os : outputStreams)
131         os.close();
132   }
133}