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