001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 The ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| 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.conqat.lib.commons.io;
018
019import java.io.IOException;
020import java.io.OutputStream;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.List;
024
025import org.conqat.lib.commons.assertion.CCSMAssert;
026import org.conqat.lib.commons.collections.CollectionUtils;
027import org.conqat.lib.commons.collections.UnmodifiableList;
028import org.conqat.lib.commons.string.StringUtils;
029
030/**
031 * This class enables multiplexing of output streams. It can be e.g. used to
032 * output content to multiple files.
033 */
034public class MultiplexedOutputStream extends OutputStream {
035
036        /** The underlying output streams. */
037        private final OutputStream[] streams;
038
039        /**
040         * Create new multiplexed output streams.
041         * 
042         * @param streams
043         *            any number of output streams.
044         */
045        public MultiplexedOutputStream(OutputStream... streams) {
046                this.streams = streams;
047        }
048
049        /**
050         * Forwards close operation to all underlying output streams.
051         * 
052         * @throws MultiIOException
053         *             if one or more of the underlying streams raised an exception
054         */
055        @Override
056        public void close() throws MultiIOException {
057                List<IOException> exceptions = new ArrayList<IOException>();
058                for (OutputStream stream : streams) {
059                        try {
060                                stream.close();
061                        } catch (IOException e) {
062                                exceptions.add(e);
063                        }
064                }
065                checkExceptions(exceptions);
066        }
067
068        /**
069         * Forwards flush operation to all underlying output streams.
070         * 
071         * @throws MultiIOException
072         *             if one or more of the underlying streams raised an exception
073         */
074        @Override
075        public void flush() throws MultiIOException {
076                List<IOException> exceptions = new ArrayList<IOException>();
077                for (OutputStream stream : streams) {
078                        try {
079                                stream.flush();
080                        } catch (IOException e) {
081                                exceptions.add(e);
082                        }
083                }
084                checkExceptions(exceptions);
085        }
086
087        /**
088         * Forwards write operation to all underlying output streams.
089         * 
090         * @throws MultiIOException
091         *             if one or more of the underlying streams raised an exception
092         */
093        @Override
094        public void write(int b) throws MultiIOException {
095                List<IOException> exceptions = new ArrayList<IOException>();
096                for (OutputStream stream : streams) {
097                        try {
098                                stream.write(b);
099                        } catch (IOException e) {
100                                exceptions.add(e);
101                        }
102                }
103                checkExceptions(exceptions);
104        }
105
106        /**
107         * Raises an {@link MultiIOException} if the provided collection is not empty.
108         */
109        private static void checkExceptions(Collection<IOException> exceptions) throws MultiIOException {
110                if (!exceptions.isEmpty()) {
111                        throw new MultiIOException(exceptions);
112                }
113        }
114
115        /** Exception class that encapsulates multiple {@link IOException}s. */
116        public static class MultiIOException extends IOException {
117
118                /** Serial version UID. */
119                private static final long serialVersionUID = 1;
120
121                /** The exceptions. */
122                private final List<IOException> exceptions = new ArrayList<IOException>();
123
124                /**
125                 * Create exception
126                 * 
127                 * @throws AssertionError
128                 *             if provided collection is empty.
129                 */
130                public MultiIOException(Collection<? extends IOException> exceptions) {
131                        CCSMAssert.isFalse(exceptions.isEmpty(), "Must have at least one exception.");
132                        this.exceptions.addAll(exceptions);
133                }
134
135                /** Returns messages of all exceptions. */
136                @Override
137                public String getMessage() {
138                        StringBuilder result = new StringBuilder();
139                        for (IOException ex : exceptions) {
140                                result.append(ex.getMessage());
141                                result.append(StringUtils.LINE_SEPARATOR);
142                        }
143                        return result.toString();
144                }
145
146                /** Get exceptions. */
147                public UnmodifiableList<IOException> getExceptions() {
148                        return CollectionUtils.asUnmodifiable(exceptions);
149                }
150        }
151}