001/* 002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved. 003 * 004 * http://www.izforge.com/izpack/ 005 * http://developer.berlios.de/projects/izpack/ 006 * 007 * Copyright 2004 Elmar Klaus Bartz 008 * 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 */ 021 022package com.izforge.izpack.util; 023 024import java.io.BufferedInputStream; 025import java.io.BufferedOutputStream; 026import java.io.File; 027import java.io.FileInputStream; 028import java.io.FileOutputStream; 029import java.io.IOException; 030import java.util.Properties; 031import java.util.StringTokenizer; 032 033/** 034 * <p> 035 * Class with some IO related helper. 036 * </p> 037 * 038 */ 039public class IoHelper 040{ 041 042 // This class uses the same values for family and flavor as 043 // TargetFactory. But this class should not depends on TargetFactory, 044 // because it is possible that TargetFactory is not bound. Therefore 045 // the definition here again. 046 047 // ------------------------------------------------------------------------ 048 // Constant Definitions 049 // ------------------------------------------------------------------------ 050 051 /** Placeholder during translatePath computing */ 052 private static final String MASKED_SLASH_PLACEHOLDER = "~&_&~"; 053 054 private static Properties envVars = null; 055 056 /** 057 * Default constructor 058 */ 059 private IoHelper() 060 { 061 } 062 063 /** 064 * Copies the contents of inFile into outFile. 065 * 066 * @param inFile path of file which should be copied 067 * @param outFile path of file to create and copy the contents of inFile into 068 */ 069 public static void copyFile(String inFile, String outFile) throws IOException 070 { 071 copyFile(new File(inFile), new File(outFile)); 072 } 073 074 /** 075 * Creates an in- and output stream for the given File objects and copies all the data from the 076 * specified input to the specified output. 077 * 078 * @param inFile File object for input 079 * @param outFile File object for output 080 * @exception IOException if an I/O error occurs 081 */ 082 public static void copyFile(File inFile, File outFile) throws IOException 083 { 084 copyFile(inFile, outFile, null, null); 085 } 086 087 /** 088 * Creates an in- and output stream for the given File objects and copies all the data from the 089 * specified input to the specified output. If permissions is not null, a chmod will be done on 090 * the output file. 091 * 092 * @param inFile File object for input 093 * @param outFile File object for output 094 * @param permissions permissions for the output file 095 * @exception IOException if an I/O error occurs 096 */ 097 public static void copyFile(File inFile, File outFile, String permissions) throws IOException 098 { 099 copyFile(inFile, outFile, permissions, null); 100 } 101 102 /** 103 * Creates an in- and output stream for the given File objects and copies all the data from the 104 * specified input to the specified output. If the VariableSubstitutor is not null, a substition 105 * will be done during copy. 106 * 107 * @param inFile File object for input 108 * @param outFile File object for output 109 * @param vss substitutor which is used during copying 110 * @exception IOException if an I/O error occurs 111 */ 112 public static void copyFile(File inFile, File outFile, VariableSubstitutor vss) 113 throws IOException 114 { 115 copyFile(inFile, outFile, null, vss); 116 } 117 118 /** 119 * Creates an in- and output stream for the given File objects and copies all the data from the 120 * specified input to the specified output. If the VariableSubstitutor is not null, a substition 121 * will be done during copy. If permissions is not null, a chmod will be done on the output 122 * file. 123 * 124 * @param inFile File object for input 125 * @param outFile File object for output 126 * @param permissions permissions for the output file 127 * @param vs substitutor which is used during copying 128 * @exception IOException if an I/O error occurs 129 */ 130 public static void copyFile(File inFile, File outFile, String permissions, 131 VariableSubstitutor vs) throws IOException 132 { 133 copyFile(inFile, outFile, permissions, vs, null); 134 } 135 136 /** 137 * Creates an in- and output stream for the given File objects and copies all the data from the 138 * specified input to the specified output. If the VariableSubstitutor is not null, a substition 139 * will be done during copy. If permissions is not null, a chmod will be done on the output 140 * file. If type is not null, that type is used as file type at substitution. 141 * 142 * @param inFile File object for input 143 * @param outFile File object for output 144 * @param permissions permissions for the output file 145 * @param vs substitutor which is used during copying 146 * @param type file type for the substitutor 147 * @exception IOException if an I/O error occurs 148 */ 149 public static void copyFile(File inFile, File outFile, String permissions, 150 VariableSubstitutor vs, String type) throws IOException 151 { 152 FileOutputStream out = new FileOutputStream(outFile); 153 FileInputStream in = new FileInputStream(inFile); 154 if (vs == null) 155 { 156 byte[] buffer = new byte[5120]; 157 long bytesCopied = 0; 158 int bytesInBuffer; 159 while ((bytesInBuffer = in.read(buffer)) != -1) 160 { 161 out.write(buffer, 0, bytesInBuffer); 162 bytesCopied += bytesInBuffer; 163 } 164 in.close(); 165 out.close(); 166 } 167 else 168 { 169 BufferedInputStream bin = new BufferedInputStream(in, 5120); 170 BufferedOutputStream bout = new BufferedOutputStream(out, 5120); 171 vs.substitute(bin, bout, type, null); 172 bin.close(); 173 bout.close(); 174 } 175 if (permissions != null && IoHelper.supported("chmod")) 176 { 177 chmod(outFile.getAbsolutePath(), permissions); 178 } 179 } 180 181 /** 182 * Creates a temp file with delete on exit rule. The extension is extracted from the template if 183 * possible, else the default extension is used. The contents of template will be copied into 184 * the temporary file. 185 * 186 * @param template file to copy from and define file extension 187 * @param defaultExtension file extension if no is contained in template 188 * @return newly created and filled temporary file 189 * @throws IOException 190 */ 191 public static File copyToTempFile(File template, String defaultExtension) throws IOException 192 { 193 return copyToTempFile(template, defaultExtension, null); 194 } 195 196 /** 197 * Creates a temp file with delete on exit rule. The extension is extracted from the template if 198 * possible, else the default extension is used. The contents of template will be copied into 199 * the temporary file. If the variable substitutor is not null, variables will be replaced 200 * during copying. 201 * 202 * @param template file to copy from and define file extension 203 * @param defaultExtension file extension if no is contained in template 204 * @param vss substitutor which is used during copying 205 * @return newly created and filled temporary file 206 * @throws IOException 207 */ 208 public static File copyToTempFile(File template, String defaultExtension, 209 VariableSubstitutor vss) throws IOException 210 { 211 String path = template.getCanonicalPath(); 212 int pos = path.lastIndexOf('.'); 213 String ext = path.substring(pos); 214 if (ext == null) ext = defaultExtension; 215 File tmpFile = File.createTempFile("izpack_io", ext); 216 tmpFile.deleteOnExit(); 217 IoHelper.copyFile(template, tmpFile, vss); 218 return tmpFile; 219 } 220 221 /** 222 * Creates a temp file with delete on exit rule. The extension is extracted from the template if 223 * possible, else the default extension is used. The contents of template will be copied into 224 * the temporary file. 225 * 226 * @param template file to copy from and define file extension 227 * @param defaultExtension file extension if no is contained in template 228 * @return newly created and filled temporary file 229 * @throws IOException 230 */ 231 public static File copyToTempFile(String template, String defaultExtension) throws IOException 232 { 233 return copyToTempFile(new File(template), defaultExtension); 234 } 235 236 /** 237 * Changes the permissions of the given file to the given POSIX permissions. 238 * 239 * @param file the file for which the permissions should be changed 240 * @param permissions POSIX permissions to be set 241 * @throws IOException if an I/O error occurs 242 */ 243 public static void chmod(File file, String permissions) throws IOException 244 { 245 chmod(file.getAbsolutePath(), permissions); 246 } 247 248 /** 249 * Changes the permissions of the given file to the given POSIX permissions. This method will be 250 * raised an exception, if the OS is not UNIX. 251 * 252 * @param path the absolute path of the file for which the permissions should be changed 253 * @param permissions POSIX permissions to be set 254 * @throws IOException if an I/O error occurs 255 */ 256 public static void chmod(String path, String permissions) throws IOException 257 { 258 // Perform UNIX 259 if (OsVersion.IS_UNIX) 260 { 261 String[] params = { "chmod", permissions, path}; 262 String[] output = new String[2]; 263 FileExecutor fe = new FileExecutor(); 264 fe.executeCommand(params, output); 265 } 266 else 267 { 268 throw new IOException("Sorry, chmod not supported yet on " + OsVersion.OS_NAME + "."); 269 } 270 } 271 272 /** 273 * Returns the free (disk) space for the given path. If it is not ascertainable -1 returns. 274 * 275 * @param path path for which the free space should be detected 276 * @return the free space for the given path 277 */ 278 public static long getFreeSpace(String path) 279 { 280 long retval = -1; 281 if (OsVersion.IS_WINDOWS) 282 { 283 String command = "cmd.exe"; 284 if (System.getProperty("os.name").toLowerCase().indexOf("windows 9") > -1) return (-1); 285 String[] params = { command, "/C", "\"dir /D /-C \"" + path + "\"\""}; 286 String[] output = new String[2]; 287 FileExecutor fe = new FileExecutor(); 288 fe.executeCommand(params, output); 289 retval = extractLong(output[0], -3, 3, "%"); 290 } 291 else if (OsVersion.IS_SUNOS) 292 { 293 String[] params = { "df", "-k", path}; 294 String[] output = new String[2]; 295 FileExecutor fe = new FileExecutor(); 296 fe.executeCommand(params, output); 297 retval = extractLong(output[0], -3, 3, "%") * 1024; 298 } 299 else if (OsVersion.IS_UNIX) 300 { 301 String[] params = { "df", "-Pk", path}; 302 String[] output = new String[2]; 303 FileExecutor fe = new FileExecutor(); 304 fe.executeCommand(params, output); 305 retval = extractLong(output[0], -3, 3, "%") * 1024; 306 } 307 return retval; 308 } 309 310 /** 311 * Returns whether the given method will be supported with the given environment. Some methods 312 * of this class are not supported on all operation systems. 313 * 314 * @param method name of the method 315 * @return true if the method will be supported with the current enivronment else false 316 * @throws RuntimeException if the given method name does not exist 317 */ 318 public static boolean supported(String method) 319 { 320 if (method.equals("getFreeSpace")) 321 { 322 if (OsVersion.IS_UNIX) return true; 323 if (OsVersion.IS_WINDOWS) 324 { // getFreeSpace do not work on Windows 98. 325 if (System.getProperty("os.name").toLowerCase().indexOf("windows 9") > -1) 326 return (false); 327 return (true); 328 } 329 } 330 else if (method.equals("chmod")) 331 { 332 if (OsVersion.IS_UNIX) return true; 333 } 334 else if (method.equals("copyFile")) 335 { 336 return true; 337 } 338 else if (method.equals("getPrimaryGroup")) 339 { 340 if (OsVersion.IS_UNIX) return true; 341 } 342 else if (method.equals("getenv")) 343 { 344 return true; 345 } 346 else 347 { 348 throw new RuntimeException("method name " + method + "not supported by this method"); 349 } 350 return false; 351 352 } 353 354 /** 355 * Returns the first existing parent directory in a path 356 * 357 * @param path path which should be scanned 358 * @return the first existing parent directory in a path 359 */ 360 public static File existingParent(File path) 361 { 362 File result = path; 363 while (!result.exists()) 364 { 365 if (result.getParent() == null) return result; 366 result = result.getParentFile(); 367 } 368 return result; 369 } 370 371 /** 372 * Extracts a long value from a string in a special manner. The string will be broken into 373 * tokens with a standard StringTokenizer. Arround the assumed place (with the given half range) 374 * the tokens are scaned reverse for a token which represents a long. if useNotIdentifier is not 375 * null, tokens which are contains this string will be ignored. The first founded long returns. 376 * 377 * @param in the string which should be parsed 378 * @param assumedPlace token number which should contain the value 379 * @param halfRange half range for detection range 380 * @param useNotIdentifier string which determines tokens which should be ignored 381 * @return founded long 382 */ 383 private static long extractLong(String in, int assumedPlace, int halfRange, 384 String useNotIdentifier) 385 { 386 long retval = -1; 387 StringTokenizer st = new StringTokenizer(in); 388 int length = st.countTokens(); 389 int i; 390 int currentRange = 0; 391 String[] interestedEntries = new String[halfRange + halfRange]; 392 for (i = 0; i < length - halfRange + assumedPlace; ++i) 393 st.nextToken(); // Forget this entries. 394 395 for (i = 0; i < halfRange + halfRange; ++i) 396 { // Put the interesting Strings into an intermediaer array. 397 if (st.hasMoreTokens()) 398 { 399 interestedEntries[i] = st.nextToken(); 400 currentRange++; 401 } 402 } 403 404 for (i = currentRange - 1; i >= 0; --i) 405 { 406 if (useNotIdentifier != null && interestedEntries[i].indexOf(useNotIdentifier) > -1) 407 continue; 408 try 409 { 410 retval = Long.parseLong(interestedEntries[i]); 411 } 412 catch (NumberFormatException nfe) 413 { 414 continue; 415 } 416 break; 417 } 418 return retval; 419 } 420 421 /** 422 * Returns the primary group of the current user. This feature will be supported only on Unix. 423 * On other systems null returns. 424 * 425 * @return the primary group of the current user 426 */ 427 public static String getPrimaryGroup() 428 { 429 if (supported("getPrimaryGroup")) 430 { 431 if (OsVersion.IS_SUNOS) 432 { // Standard id of SOLARIS do not support -gn. 433 String[] params = { "id"}; 434 String[] output = new String[2]; 435 FileExecutor fe = new FileExecutor(); 436 fe.executeCommand(params, output); 437 // No we have "uid=%u(%s) gid=%u(%s)" 438 if (output[0] != null) 439 { 440 StringTokenizer st = new StringTokenizer(output[0], "()"); 441 int length = st.countTokens(); 442 if (length >= 4) 443 { 444 for (int i = 0; i < 3; ++i) 445 st.nextToken(); 446 return (st.nextToken()); 447 } 448 } 449 return (null); 450 } 451 else 452 { 453 String[] params = { "id", "-gn"}; 454 String[] output = new String[2]; 455 FileExecutor fe = new FileExecutor(); 456 fe.executeCommand(params, output); 457 return output[0]; 458 } 459 } 460 else 461 return null; 462 } 463 464 /** 465 * Returns a string resulting from replacing all occurrences of what in this string with with. 466 * In opposite to the String.replaceAll method this method do not use regular expression or 467 * other methods which are only available in JRE 1.4 and later. This method was special made to 468 * mask masked slashes to avert a conversion during path translation. 469 * 470 * @param destination string for which the replacing should be performed 471 * @param what what string should be replaced 472 * @param with with what string what should be replaced 473 * @return a new String object if what was found in the given string, else the given string self 474 */ 475 public static String replaceString(String destination, String what, String with) 476 { 477 if (destination.indexOf(what) >= 0) 478 { // what found, with (placeholder) not included in destination -> 479 // perform changing. 480 StringBuffer buf = new StringBuffer(); 481 int last = 0; 482 int current = destination.indexOf(what); 483 int whatLength = what.length(); 484 while (current >= 0) 485 { // Do not use Methods from JRE 1.4 and higher ... 486 if (current > 0) buf.append(destination.substring(last, current)); 487 buf.append(with); 488 last = current + whatLength; 489 current = destination.indexOf(what, last); 490 } 491 if (destination.length() > last) buf.append(destination.substring(last)); 492 return buf.toString(); 493 } 494 return destination; 495 } 496 497 /** 498 * Translates a relative path to a local system path. 499 * 500 * @param destination The path to translate. 501 * @return The translated path. 502 */ 503 public static String translatePath(String destination, VariableSubstitutor vs) 504 { 505 // Parse for variables 506 destination = vs.substitute(destination, null); 507 508 // Convert the file separator characters 509 510 // destination = destination.replace('/', File.separatorChar); 511 // Undo the conversion if the slashes was masked with 512 // a backslash 513 514 // Not all occurencies of slashes are path separators. To differ 515 // between it we allow to mask a slash with a backslash infront. 516 // Unfortunately we cannot use String.replaceAll because it 517 // handles backslashes in the replacement string in a special way 518 // and the method exist only beginning with JRE 1.4. 519 // Therefore the little bit crude way following ... 520 if (destination.indexOf("\\/") >= 0 && destination.indexOf(MASKED_SLASH_PLACEHOLDER) < 0) 521 { // Masked slash found, placeholder not included in destination -> 522 // perform masking. 523 destination = replaceString(destination, "\\/", MASKED_SLASH_PLACEHOLDER); 524 // Masked slashes changed to MASKED_SLASH_PLACEHOLDER. 525 // Replace unmasked slashes. 526 destination = destination.replace('/', File.separatorChar); 527 // Replace the MASKED_SLASH_PLACEHOLDER to slashes; masking 528 // backslashes will 529 // be removed. 530 destination = replaceString(destination, MASKED_SLASH_PLACEHOLDER, "/"); 531 } 532 else 533 destination = destination.replace('/', File.separatorChar); 534 return destination; 535 } 536 537 /** 538 * Returns the value of the environment variable given by key. This method is a work around for 539 * VM versions which do not support getenv in an other way. At the first call all environment 540 * variables will be loaded via an exec. On Windows keys are not case sensitive. 541 * 542 * @param key variable name for which the value should be resolved 543 * @return the value of the environment variable given by key 544 */ 545 public static String getenv(String key) 546 { 547 if (envVars == null) loadEnv(); 548 if (envVars == null) return (null); 549 if (OsVersion.IS_WINDOWS) key = key.toUpperCase(); 550 return (String) (envVars.get(key)); 551 } 552 553 /** 554 * Loads all environment variables via an exec. 555 */ 556 private static void loadEnv() 557 { 558 String[] output = new String[2]; 559 String[] params; 560 if (OsVersion.IS_WINDOWS) 561 { 562 String command = "cmd.exe"; 563 if (System.getProperty("os.name").toLowerCase().indexOf("windows 9") > -1) 564 command = "command.com"; 565 String[] paramst = { command, "/C", "set"}; 566 params = paramst; 567 } 568 else 569 { 570 String[] paramst = { "env"}; 571 params = paramst; 572 } 573 FileExecutor fe = new FileExecutor(); 574 fe.executeCommand(params, output); 575 if (output[0].length() <= 0) return; 576 String lineSep = System.getProperty("line.separator"); 577 StringTokenizer st = new StringTokenizer(output[0], lineSep); 578 envVars = new Properties(); 579 String var = null; 580 while (st.hasMoreTokens()) 581 { 582 String line = st.nextToken(); 583 if (line.indexOf('=') == -1) 584 { // May be a env var with a new line in it. 585 if (var == null) 586 { 587 var = lineSep + line; 588 } 589 else 590 { 591 var += lineSep + line; 592 } 593 } 594 else 595 { // New var, perform the previous one. 596 setEnvVar(var); 597 var = line; 598 } 599 } 600 setEnvVar(var); 601 } 602 603 /** 604 * Extracts key and value from the given string var. The key should be separated from the value 605 * by a sign. On Windows all chars of the key are translated to upper case. 606 * 607 * @param var 608 */ 609 private static void setEnvVar(String var) 610 { 611 if (var == null) return; 612 int index = var.indexOf('='); 613 if (index < 0) return; 614 String key = var.substring(0, index); 615 // On windows change all key chars to upper. 616 if (OsVersion.IS_WINDOWS) key = key.toUpperCase(); 617 envVars.setProperty(key, var.substring(index + 1)); 618 619 } 620}