001/*
002 * Copyright 2011-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2011-2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util;
022
023
024
025import java.io.OutputStream;
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collection;
030import java.util.Collections;
031import java.util.List;
032
033
034
035/**
036 * This class provides an {@code OutputStream} implementation that can cause
037 * everything provided to it to be written to multiple output streams (e.g.,
038 * to both a file and to standard output, or to both a file and a network
039 * socket).  Any number of destination streams (including zero, if desired) may
040 * be specified.
041 */
042@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
043public final class TeeOutputStream
044       extends OutputStream
045{
046  // The set of target output streams to which any data received will be
047  // written.
048  private final List<OutputStream> streams;
049
050
051
052  /**
053   * Creates a new instance of this output stream that will write any data
054   * received to each of the provided target streams.
055   *
056   * @param  targetStreams  The set of output streams to which any data received
057   *                        will be written.  If it is {@code null} or empty,
058   *                        then any data received will simply be discarded.
059   */
060  public TeeOutputStream(final OutputStream... targetStreams)
061  {
062    if (targetStreams == null)
063    {
064      streams = Collections.emptyList();
065    }
066    else
067    {
068      streams = Collections.unmodifiableList(
069           new ArrayList<>(Arrays.asList(targetStreams)));
070    }
071  }
072
073
074
075  /**
076   * Creates a new instance of this output stream that will write any data
077   * received to each of the provided target streams.
078   *
079   * @param  targetStreams  The set of output streams to which any data received
080   *                        will be written.  If it is {@code null} or empty,
081   *                        then any data received will simply be discarded.
082   */
083  public TeeOutputStream(final Collection<? extends OutputStream> targetStreams)
084  {
085    if (targetStreams == null)
086    {
087      streams = Collections.emptyList();
088    }
089    else
090    {
091      streams = Collections.unmodifiableList(new ArrayList<>(targetStreams));
092    }
093  }
094
095
096
097  /**
098   * Writes the provided byte of data to each of the target output streams.
099   *
100   * @param  b  The byte of data to be written.  Only the lower eight bits
101   *            of the provided value will be written.
102   *
103   * @throws  IOException  If a problem occurs while writing the provided byte
104   *                       to any of the target output streams.
105   */
106  @Override()
107  public void write(final int b)
108         throws IOException
109  {
110    for (final OutputStream s : streams)
111    {
112      s.write(b);
113    }
114  }
115
116
117
118  /**
119   * Writes the entire contents of the provided byte array to each of the target
120   * output streams.
121   *
122   * @param  b  The byte array containing the data to be written.
123   *
124   * @throws  IOException  If a problem occurs while writing the provided data
125   *                       to any of the target output streams.
126   */
127  @Override()
128  public void write(final byte[] b)
129         throws IOException
130  {
131    for (final OutputStream s : streams)
132    {
133      s.write(b);
134    }
135  }
136
137
138
139  /**
140   * Writes a portion of the contents of the provided byte array to each of the
141   * target output streams.
142   *
143   * @param  b    The byte array containing the data to be written.
144   * @param  off  The offset within the array at which the data should start
145   *              being written.
146   * @param  len  The number of bytes from the array that should be written.
147   *
148   * @throws  IOException  If a problem occurs while writing the provided data
149   *                       to any of the target output streams.
150   */
151  @Override()
152  public void write(final byte[] b, final int off, final int len)
153         throws IOException
154  {
155    for (final OutputStream s : streams)
156    {
157      s.write(b, off, len);
158    }
159  }
160
161
162
163  /**
164   * Flushes each of the target output streams to force any buffered content to
165   * be written out.
166   *
167   * @throws  IOException  If a problem occurs while flushing any of the target
168   *                       output streams.
169   */
170  @Override()
171  public void flush()
172         throws IOException
173  {
174    for (final OutputStream s : streams)
175    {
176      s.flush();
177    }
178  }
179
180
181
182  /**
183   * Closes each of the target output streams.
184   *
185   * @throws  IOException  If a problem occurs while closing any of the target
186   *                       output streams.  Note that even if an exception is
187   *                       thrown, an attempt will be made to close all target
188   *                       streams.  If multiple target streams throw an
189   *                       exception, then the first exception encountered will
190   *                       be thrown.
191   */
192  @Override()
193  public void close()
194         throws IOException
195  {
196    IOException exceptionToThrow = null;
197
198    for (final OutputStream s : streams)
199    {
200      try
201      {
202        s.close();
203      }
204      catch (final IOException ioe)
205      {
206        Debug.debugException(ioe);
207        if (exceptionToThrow == null)
208        {
209          exceptionToThrow = ioe;
210        }
211      }
212    }
213
214    if (exceptionToThrow != null)
215    {
216      throw exceptionToThrow;
217    }
218  }
219}