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}