001    /*
002        Licensed to the Apache Software Foundation (ASF) under one
003        or more contributor license agreements.  See the NOTICE file
004        distributed with this work for additional information
005        regarding copyright ownership.  The ASF licenses this file
006        to you under the Apache License, Version 2.0 (the
007        "License"); you may not use this file except in compliance
008        with the License.  You may obtain a copy of the License at
009    
010           http://www.apache.org/licenses/LICENSE-2.0
011    
012        Unless required by applicable law or agreed to in writing,
013        software distributed under the License is distributed on an
014        "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015        KIND, either express or implied.  See the License for the
016        specific language governing permissions and limitations
017        under the License.   
018     */
019    package org.apache.wiki.util;
020    
021    import java.io.BufferedReader;
022    import java.io.ByteArrayOutputStream;
023    import java.io.File;
024    import java.io.FileOutputStream;
025    import java.io.IOException;
026    import java.io.InputStream;
027    import java.io.InputStreamReader;
028    import java.io.OutputStream;
029    import java.io.OutputStreamWriter;
030    import java.io.Reader;
031    import java.io.StringReader;
032    import java.io.StringWriter;
033    import java.io.Writer;
034    import java.nio.ByteBuffer;
035    import java.nio.CharBuffer;
036    import java.nio.charset.CharacterCodingException;
037    import java.nio.charset.Charset;
038    import java.nio.charset.CharsetDecoder;
039    import java.nio.charset.CodingErrorAction;
040    
041    import org.apache.log4j.Logger;
042    
043    /**
044     *  Generic utilities related to file and stream handling.
045     */
046    public final class FileUtil
047    {
048        /** Size of the buffer used when copying large chunks of data. */
049        private static final int      BUFFER_SIZE = 4096;
050        private static final Logger   log         = Logger.getLogger(FileUtil.class);
051    
052        /**
053         *  Private constructor prevents instantiation.
054         */
055        private FileUtil()
056        {}
057    
058        /**
059         *  Makes a new temporary file and writes content into it. The temporary
060         *  file is created using <code>File.createTempFile()</code>, and the usual
061         *  semantics apply.  The files are not deleted automatically in exit.
062         *
063         *  @param content Initial content of the temporary file.
064         *  @param encoding Encoding to use.
065         *  @return The handle to the new temporary file
066         *  @throws IOException If the content creation failed.
067         *  @see java.io.File#createTempFile(String,String,File)
068         */
069        public static File newTmpFile( String content, String encoding )
070            throws IOException
071        {
072            Writer out = null;
073            Reader in  = null;
074            File   f   = null;
075    
076            try
077            {
078                f = File.createTempFile( "jspwiki", null );
079    
080                in = new StringReader( content );
081    
082                out = new OutputStreamWriter( new FileOutputStream( f ),
083                                              encoding );
084    
085                copyContents( in, out );
086            }
087            finally
088            {
089                if( in  != null ) in.close();
090                if( out != null ) out.close();
091            }
092    
093            return f;
094        }
095    
096        /**
097         *  Creates a new temporary file using the default encoding
098         *  of ISO-8859-1 (Latin1).
099         *
100         *  @param content The content to put into the file.
101         *  @throws IOException If writing was unsuccessful.
102         *  @return A handle to the newly created file.
103         *  @see #newTmpFile( String, String )
104         *  @see java.io.File#createTempFile(String,String,File)
105         */
106        public static File newTmpFile( String content )
107            throws IOException
108        {
109            return newTmpFile( content, "ISO-8859-1" );
110        }
111    
112        /**
113         *  Runs a simple command in given directory.
114         *  The environment is inherited from the parent process (e.g. the
115         *  one in which this Java VM runs).
116         *
117         *  @return Standard output from the command.
118         *  @param  command The command to run
119         *  @param  directory The working directory to run the command in
120         *  @throws IOException If the command failed
121         *  @throws InterruptedException If the command was halted
122         */
123        public static String runSimpleCommand( String command, String directory )
124            throws IOException,
125                   InterruptedException
126        {
127            StringBuffer result = new StringBuffer();
128    
129            log.info("Running simple command "+command+" in "+directory);
130    
131            Process process = Runtime.getRuntime().exec( command, null, new File(directory) );
132    
133            BufferedReader stdout = null;
134            BufferedReader stderr = null;
135    
136            try
137            {
138                stdout = new BufferedReader( new InputStreamReader(process.getInputStream()) );
139                stderr = new BufferedReader( new InputStreamReader(process.getErrorStream()) );
140    
141                String line;
142    
143                while( (line = stdout.readLine()) != null )
144                {
145                    result.append( line+"\n");
146                }
147    
148                StringBuffer error = new StringBuffer();
149                while( (line = stderr.readLine()) != null )
150                {
151                    error.append( line+"\n");
152                }
153    
154                if( error.length() > 0 )
155                {
156                    log.error("Command failed, error stream is: "+error);
157                }
158    
159                process.waitFor();
160    
161            }
162            finally
163            {
164                // we must close all by exec(..) opened streams: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4784692
165                process.getInputStream().close();
166                if( stdout != null ) stdout.close();
167                if( stderr != null ) stderr.close();
168            }
169    
170            return result.toString();
171        }
172    
173    
174        /**
175         *  Just copies all characters from <I>in</I> to <I>out</I>.  The copying
176         *  is performed using a buffer of bytes.
177         *
178         *  @since 1.5.8
179         *  @param in The reader to copy from
180         *  @param out The reader to copy to
181         *  @throws IOException If reading or writing failed.
182         */
183        public static void copyContents( Reader in, Writer out )
184            throws IOException
185        {
186            char[] buf = new char[BUFFER_SIZE];
187            int bytesRead = 0;
188    
189            while ((bytesRead = in.read(buf)) > 0)
190            {
191                out.write(buf, 0, bytesRead);
192            }
193    
194            out.flush();
195        }
196    
197        /**
198         *  Just copies all bytes from <I>in</I> to <I>out</I>.  The copying is
199         *  performed using a buffer of bytes.
200         *
201         *  @since 1.9.31
202         *  @param in The inputstream to copy from
203         *  @param out The outputstream to copy to
204         *  @throws IOException In case reading or writing fails.
205         */
206        public static void copyContents( InputStream in, OutputStream out )
207            throws IOException
208        {
209            byte[] buf = new byte[BUFFER_SIZE];
210            int bytesRead = 0;
211    
212            while ((bytesRead = in.read(buf)) > 0)
213            {
214                out.write(buf, 0, bytesRead);
215            }
216    
217            out.flush();
218        }
219    
220        /**
221         *  Reads in file contents.
222         *  <P>
223         *  This method is smart and falls back to ISO-8859-1 if the input stream does not
224         *  seem to be in the specified encoding.
225         *
226         *  @param input The InputStream to read from.
227         *  @param encoding The encoding to assume at first.
228         *  @return A String, interpreted in the "encoding", or, if it fails, in Latin1.
229         *  @throws IOException If the stream cannot be read or the stream cannot be
230         *          decoded (even) in Latin1
231         */
232        public static String readContents( InputStream input, String encoding )
233            throws IOException
234        {
235            ByteArrayOutputStream out = new ByteArrayOutputStream();
236            FileUtil.copyContents( input, out );
237    
238            ByteBuffer     bbuf        = ByteBuffer.wrap( out.toByteArray() );
239    
240            Charset        cset        = Charset.forName( encoding );
241            CharsetDecoder csetdecoder = cset.newDecoder();
242    
243            csetdecoder.onMalformedInput( CodingErrorAction.REPORT );
244            csetdecoder.onUnmappableCharacter( CodingErrorAction.REPORT );
245    
246            try
247            {
248                CharBuffer cbuf = csetdecoder.decode( bbuf );
249    
250                return cbuf.toString();
251            }
252            catch( CharacterCodingException e )
253            {
254                Charset        latin1    = Charset.forName("ISO-8859-1");
255                CharsetDecoder l1decoder = latin1.newDecoder();
256    
257                l1decoder.onMalformedInput( CodingErrorAction.REPORT );
258                l1decoder.onUnmappableCharacter( CodingErrorAction.REPORT );
259    
260                try
261                {
262                    bbuf = ByteBuffer.wrap( out.toByteArray() );
263    
264                    CharBuffer cbuf = l1decoder.decode( bbuf );
265    
266                    return cbuf.toString();
267                }
268                catch( CharacterCodingException ex )
269                {
270                    throw (CharacterCodingException) ex.fillInStackTrace();
271                }
272            }
273        }
274    
275        /**
276         *  Returns the full contents of the Reader as a String.
277         *
278         *  @since 1.5.8
279         *  @param in The reader from which the contents shall be read.
280         *  @return String read from the Reader
281         *  @throws IOException If reading fails.
282         */
283        public static String readContents( Reader in )
284            throws IOException
285        {
286            Writer out = null;
287    
288            try
289            {
290                out = new StringWriter();
291    
292                copyContents( in, out );
293    
294                return out.toString();
295            }
296            finally
297            {
298                try
299                {
300                    out.close();
301                }
302                catch( Exception e )
303                {
304                    log.error("Not able to close the stream while reading contents.");
305                }
306            }
307        }
308    
309        /**
310         *  Returns the class and method name (+a line number) in which the
311         *  Throwable was thrown.
312         *
313         *  @param t A Throwable to analyze.
314         *  @return A human-readable string stating the class and method.  Do not rely
315         *          the format to be anything fixed.
316         */
317        public static String getThrowingMethod( Throwable t )
318        {
319            StackTraceElement[] trace = t.getStackTrace();
320            StringBuffer sb = new StringBuffer();
321    
322            if( trace == null || trace.length == 0 )
323            {
324                sb.append( "[Stack trace not available]" );
325            }
326            else
327            {
328                sb.append( trace[0].isNativeMethod() ? "native method" : "" );
329                sb.append( trace[0].getClassName() );
330                sb.append(".");
331                sb.append(trace[0].getMethodName()+"(), line "+trace[0].getLineNumber());
332            }
333            return sb.toString();
334        }
335    }