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 */
019package org.apache.wiki.util;
020
021import java.io.BufferedReader;
022import java.io.ByteArrayOutputStream;
023import java.io.File;
024import java.io.FileOutputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.InputStreamReader;
028import java.io.OutputStream;
029import java.io.OutputStreamWriter;
030import java.io.Reader;
031import java.io.StringReader;
032import java.io.StringWriter;
033import java.io.Writer;
034import java.nio.ByteBuffer;
035import java.nio.CharBuffer;
036import java.nio.charset.CharacterCodingException;
037import java.nio.charset.Charset;
038import java.nio.charset.CharsetDecoder;
039import java.nio.charset.CodingErrorAction;
040
041import org.apache.log4j.Logger;
042
043/**
044 *  Generic utilities related to file and stream handling.
045 */
046public 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        StringBuilder result = new StringBuilder();
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            StringBuilder error = new StringBuilder();
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        StringBuilder sb = new StringBuilder();
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}