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