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