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