123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745 |
- /* *******************************************************************
- * Copyright (c) 1999-2000 Xerox Corporation.
- * All rights reserved.
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Xerox/PARC initial implementation
- * ******************************************************************/
-
-
- package org.aspectj.testing.util;
-
- import java.io.File;
- import java.io.FileFilter;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.io.PrintWriter;
- import java.io.StringBufferInputStream;
- import java.io.StringWriter;
- import java.net.MalformedURLException;
- import java.net.URL;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Enumeration;
- import java.util.Iterator;
- import java.util.Vector;
- import java.util.jar.Attributes;
- import java.util.jar.JarOutputStream;
- import java.util.jar.Manifest;
- import java.util.zip.ZipEntry;
- import java.util.zip.ZipFile;
- import java.util.zip.ZipOutputStream;
-
- /**
- * misc file utilities
- */
- public class FileUtil {
-
- /** default filename if URL has none (i.e., a directory URL): index.html */
- public static final String DEFAULT_URL_FILENAME = "index.html";
-
- /**
- * @param args the String[]
- * <code>{ "-copy", "-srcFile" | "-srcUrl", {src}, "-destFile", {destFile} }</code>
- */
- public static void main (String[] args) {
- if (null == args) return;
- for (int i = 0; (i+4) < args.length; i++) {
- if ("-copy".equals(args[i])) {
- String arg = args[++i];
- String src = null;
- String destFile = null;
- boolean srcIsFile = ("-srcFile".equals(arg));
- if (srcIsFile) {
- src = args[++i];
- } else if ("-srcUrl".equals(arg)) {
- src = args[++i];
- }
- if ((null != src) && ("-destFile".equals(args[++i]))) {
- destFile = args[++i];
- StringBuffer errs = new StringBuffer();
- if (srcIsFile) {
- copyFile(new File(src), new File(destFile), errs);
- } else {
- URL url = null;
- try { url = new URL(src) ; }
- catch (MalformedURLException e) { render(e, errs); }
- if (null != url) {
- copyURL(url, new File(destFile), errs);
- }
- }
- if (0 < errs.length()) {
- System.err.println("Error copying " + src + " to " + destFile);
- System.err.println(errs.toString());
-
- }
- }
- } // ("-copy".equals(args[i])){
- }
- } // end of main ()
-
- /**
- * Generate a list of missing and extra files by comparison to a
- * timestamp, optionally excluding certain files.
- * This is a call to select all files after a given time:
- *
- * <pre>Diffs d = dirDiffs(dir, givenTime, null, null, null);</pre>
- *
- * Given files
- * <pre>classes/Foo.class
- * classes/bar/Bash.class
- * classes/Old.class
- * classes/one/Unexpected.class
- * classes/start.gif</pre>
- * where only Old.class predated startTime, this is a call that
- * reports "one/Unexpected.class" as unexpected and "Foo"
- * as missing:
- * <pre>String requireSuffix = ".class";
- * String[] expectedPaths = new String[] { "Foo", "bar/Bas" };
- * File file = new File("classes");
- * Diffs d = dirDiffs(dir, startTime, requireSuffix,expectedPaths, true);</pre>
- *
- * @param label the String to use for the Diffs label
- * @param dir the File for the dir to search
- * @param startTime collect files modified after this time
- * (ignored if less than 0)
- * @param requireSuffix ignore all actual files without this suffix
- * (ignored if null)
- * @param expectedPaths paths (relative to dir) of the expected files
- * (if null, none expected)
- * @param acceptFilePrefix if true, then accept a file which
- * differs from an expected file name only by a suffix
- * (which need not begin with ".").
- */
- public static Diffs dirDiffs( // XXX too complicated, weak prefix checking
- final String label,
- final File dir,
- final long startTime,
- final String requiredSuffix,
- final String[] expectedPaths,
- final boolean acceptFilePrefix) {
-
- LangUtil.throwIaxIfNull(dir, "dir");
- final boolean checkExpected = !LangUtil.isEmpty(expectedPaths);
-
- // normalize sources to ignore
- final ArrayList expected = (!checkExpected ? null : new ArrayList());
- if (checkExpected) {
- for (int i = 0; i < expectedPaths.length; i++) {
- String srcPath = expectedPaths[i];
- if (!LangUtil.isEmpty(srcPath)) {
- expected.add(org.aspectj.util.FileUtil.weakNormalize(srcPath));
- }
- }
- }
-
- // gather, normalize paths changed
- FileFilter touchedCollector = new FileFilter() {
- /**
- * For files complying with time and suffix rules,
- * return true (accumulate - unexpected)
- * unless they match expected files,
- * (deleting any matches from sources
- * so the remainder is missing).
- * @return true for unexpected files after date */
- public boolean accept(File file) {
- if (file.isFile()
- && ((0 > startTime)
- || (startTime < file.lastModified()))) {
- String path = file.getPath();
- if ((null == requiredSuffix) || path.endsWith(requiredSuffix)) {
- path = org.aspectj.util.FileUtil.weakNormalize(path);
- if (checkExpected) {
- if (!acceptFilePrefix) {
- // File.equals(..) does lexical compare
- if (expected.contains(path)) {
- expected.remove(path);
- // found - do not add to unexpected
- return false;
- }
- } else {
- for (Iterator iter = expected.iterator();
- iter.hasNext();
- ) {
- String exp = (String) iter.next();
- if (path.startsWith(exp)) {
- String suffix = path.substring(exp.length());
- if (-1 == suffix.indexOf("/")) { // normalized...
- expected.remove(path);
- // found - do not add to unexpected
- return false;
- }
- }
- }
- }
- }
- // add if is file, right time, and have or don't need suffix
- return true;
- }
- }
- // skip if not file or not right time
- return false;
- }
- };
- ArrayList unexp = new ArrayList();
- unexp.addAll(Arrays.asList(dir.listFiles(touchedCollector)));
-
- // report any unexpected changes
- return Diffs.makeDiffs(label, expected, unexp, String.CASE_INSENSITIVE_ORDER);
- }
-
-
- /**
- * Visit the entries in a zip file, halting when visitor balks.
- * Errors are silently ignored.
- * @throws IllegalArgumentException if zipfile or visitor is null
- */
- public static void visitZipEntries(ZipFile zipfile, StringVisitor visitor) {
- visitZipEntries(zipfile, visitor, (StringBuffer) null);
- }
-
- /**
- * Visit the entries in a zip file, halting when visitor balks.
- * Errors are reported in errs, if not null.
- * @throws IllegalArgumentException if zipfile or visitor is null
- */
- public static void visitZipEntries(ZipFile zipfile, StringVisitor visitor,
- StringBuffer errs) {
- if (null == zipfile) throw new IllegalArgumentException("null zipfile");
- if (null == visitor) throw new IllegalArgumentException("null visitor");
- int index = 0;
- try {
- Enumeration enu = zipfile.entries();
- while (enu.hasMoreElements()) {
- ZipEntry entry = (ZipEntry) enu.nextElement();
- index++;
- if (! visitor.accept(entry.getName())) {
- break;
- }
- }
- } catch (Throwable e) {
- if (null != errs) {
- errs.append("FileUtil.visitZipEntries error accessing entry " + index
- + ": " + e.getMessage());
- StringWriter sw = new StringWriter();
- e.printStackTrace(new PrintWriter(sw));
- errs.append(sw.toString());
- }
- } finally {
- if (null != zipfile) {
- try { zipfile.close(); }
- catch (IOException x) {} // ignore
- }
- }
- }
-
- /**
- * descend filesystem tree, invoking FileFilter.accept() on files.
- * E.g., To list files from current directory:
- * <code><pre>descendFileTree(new File("."), new FileFilter() {
- * public boolean accept(File f){
- * System.out.println(f.getAbsolutePath());
- * return true;
- * }});</code></pre>
- * @param file root/starting point. If a file, the only one visited.
- * @param filter supplies accept(File) routine
- */
- public static void descendFileTree(File file, FileFilter filter) {
- descendFileTree(file, filter, false);
- }
-
- /**
- * Descend filesystem tree, invoking FileFilter.accept() on files
- * and, if userRecursion, on dirs. If userRecursion, accept() must
- * call descendFileTree() again to recurse down directories.
- * This calls fileFilter.accept(File) on all files before doing any dirs.
- * E.g., To list only files from Unix root:
- * <code><pre>descendFileTree(new File("/"), new FileFilter() {
- * public boolean run(File f){
- * System.out.println(f.getAbsolutePath());
- * return true;
- * }}, false);</code></pre>
- * To list files/dir from root using user recursion:
- * <code><pre>descendFileTree(new File("/"), new FileFilter() {
- * public boolean run(File f){
- * System.out.println(f.getAbsolutePath());
- * if (f.isDirectory() && (-1 == f.getName().indexOf("CVS")))
- * return descendFileTree(f, this, true);
- * return true;
- * }}, true);</code></pre>
- * @param file root/starting point. If a file, the only one visited.
- * @param filter supplies boolean accept(File) method
- * @param userRecursion - if true, do accept() on dirs; else, recurse
- * @return false if any fileFilter.accept(File) did.
- * @throws IllegalArgumentException if file or fileFilter is null
- */
- public static boolean descendFileTree(File file, FileFilter fileFilter,
- boolean userRecursion) {
- if (null == file) {throw new IllegalArgumentException("parm File"); }
- if (null == fileFilter){throw new IllegalArgumentException("parm FileFilter");}
-
- if (!file.isDirectory()) {
- return fileFilter.accept(file);
- } else if (file.canRead()) {
- // go through files first
- File[] files = file.listFiles(ValidFileFilter.FILE_EXISTS);
- if (null != files) {
- for (int i = 0; i < files.length; i++) {
- if (!fileFilter.accept(files[i])) {
- return false;
- }
- }
- }
- // now recurse to handle directories
- File[] dirs = file.listFiles(ValidFileFilter.DIR_EXISTS);
- if (null != dirs) {
- for (int i = 0; i < dirs.length; i++) {
- if (userRecursion) {
- if (!fileFilter.accept(dirs[i])) {
- return false;
- }
- } else {
- if (!descendFileTree(dirs[i], fileFilter,userRecursion)) {
- return false;
- }
- }
- }
- }
- } // readable directory (ignore unreadable ones)
- return true;
- } // descendFiles
-
- /**
- * Return the names of all files below a directory.
- * If file is a directory, then all files under the directory
- * are returned. If file is absolute or relative, all the files are.
- * If file is a zip or jar file, then all entries in the zip or jar
- * are listed. Entries inside those jarfiles/zipfiles are not listed.
- * There are no guarantees about ordering.
- * @param dir the File to list for
- * @param results the Collection to use for the results (may be null)
- * @throws IllegalArgumentException if null == dir
- * @return a Collection of String of paths, including paths inside jars
- */
- public static Collection<String> directoryToString(File dir, Collection results) {
- if (null == dir) throw new IllegalArgumentException("null dir");
- final Collection<String> result = (results != null? results : new Vector());
- if (isZipFile(dir)) {
- zipFileToString(dir, result);
- } else if (!dir.isDirectory()) {
- throw new IllegalArgumentException("not a dir: " + dir);
- } else {
- AccumulatingFileFilter acFilter = new AccumulatingFileFilter() {
- public boolean accumulate(File file) {
- String name = file.getPath();
- result.add(name);
- if (isZipFile(file)) {
- zipFileToString(file, result);
- }
- return true;
- }
- };
- descendFileTree(dir, acFilter, false);
- }
- return result;
- } // directoryToString
-
- /**
- * Render as String the entries in a zip or jar file,
- * converting each to String beforehand (as jarpath!jarentry)
- * applying policies for whitespace, etc.
- * @param file the File to enumerate ZipEntry for
- * @param results the Colection to use to return the FileLine - may be null
- * @return FileLines with string as text and
- * canonical as string modified by any canonicalizing policies.
- */
- public static Collection zipFileToString(final File zipfile, Collection results) {
- Collection result = (results != null ? results : new Vector());
- ZipFile zip = null;
- try {
- zip = new ZipFile(zipfile); // ZipFile.OPEN_READ| ZipFile.OPEN_DELETE); delete is 1.3 only
- Enumeration enu = zip.entries();
- while (enu.hasMoreElements()) {
- results.add(renderZipEntry(zipfile, (ZipEntry) enu.nextElement()));
- }
- zip.close();
- zip = null;
- } catch (Throwable t) {
- String err = "Error opening " + zipfile + " attempting to continue...";
- System.err.println(err);
- t.printStackTrace(System.err);
- } finally {
- if (null != zip) {
- try { zip.close(); }
- catch (IOException e) {
- e.printStackTrace(System.err);
- }
- }
- }
- return result;
- }
-
- /**
- * @return true if file represents an existing file with a zip extension
- */
- public static boolean isZipFile(File f) {
- String s = null;
- if ((null == f) || (null == (s = f.getPath()))) {
- return false;
- } else {
- return (f.canRead() && !f.isDirectory()
- && (s.endsWith(".zip")
- || (s.endsWith(".jar"))));
- }
- }
-
- /**
- * Render a zip/entry combination to String
- */
- public static String renderZipEntry(File zipfile, ZipEntry entry) {
- String filename = (null == zipfile ? "null File" : zipfile.getName());
- String entryname = (null == entry ? "null ZipEntry" : entry.getName());
- return filename + "!" + entryname;
- }
-
- /**
- * Write all files in directory out to jarFile
- * @param jarFile the File to create and write to
- * @param directory the File representing the directory to read
- * @param mainClass the value of the main class attribute - may be null
- */
- public static boolean createJarFile(File jarFile, File directory,
- String mainClass, FileFilter filter) {
- String label = "createJarFile("+jarFile
- +","+directory +","+mainClass +","+filter + "): ";
- Log.signal(label + " start");
- if (null == directory)
- throw new IllegalArgumentException("null directory");
- Manifest manifest = createManifest(mainClass);
- Log.signal(label + " manifest=" + manifest);
- JarOutputStream out = null;
- try {
- File jarFileDir = jarFile.getParentFile();
- if (null == jarFileDir) {
- Log.signal(label + " null jarFileDir");
- } else if (!jarFileDir.exists() && !jarFileDir.mkdirs()) { // XXX convert to Error
- Log.signal(label + " unable to create jarFileDir: " + jarFileDir);
- }
- OutputStream os = new FileOutputStream(jarFile);
- out = (null == manifest ? new JarOutputStream(os)
- : new JarOutputStream(os, manifest));
- Log.signal(label + " out=" + out);
- ZipAccumulator reader = new ZipAccumulator(directory, out, filter);
- Log.signal(label + " reader=" + reader);
- FileUtil.descendFileTree(directory, reader);
- out.closeEntry();
- return true;
- } catch (IOException e) {
- e.printStackTrace(System.err); // todo
- } finally {
- if (null != out) {
- try { out.close();}
- catch (IOException e) {} // todo ignored
- }
- }
-
- return false;
- }
-
- protected static Manifest createManifest(String mainClass) {
- final String mainKey = "Main-Class";
- Manifest result = null;
- if (null != mainClass) {
- String entry = "Manifest-Version: 1.0\n"
- + mainKey + ": " + mainClass + "\n";
- try {
- result = new Manifest(new StringBufferInputStream(entry));
- Attributes attributes = result.getMainAttributes();
- String main = attributes.getValue(mainKey);
- if (null == main) {
- attributes.putValue(mainKey, mainClass);
- main = attributes.getValue(mainKey);
- if (null == main) {
- Log.signal("createManifest unable to set main "
- + mainClass);
- }
- }
- } catch (IOException e) { // todo ignoring
- Log.signal(e, " IOException creating manifest with " + mainClass);
- }
- }
- return result;
- }
-
-
- /** read a file out to the zip stream */
- protected static void addFileToZip(File in, File parent,
- ZipOutputStream out)
- throws IOException {
- String path = in.getCanonicalPath();
- String parentPath = parent.getCanonicalPath();
- if (!path.startsWith(parentPath)) {
- throw new Error("not parent: " + parentPath + " of " + path);
- } else {
- path = path.substring(1+parentPath.length());
- path = path.replace('\\', '/'); // todo: use filesep
- }
- ZipEntry entry = new ZipEntry(path);
- entry.setTime(in.lastModified());
- // todo: default behavior is DEFLATED
-
- out.putNextEntry(entry);
-
- InputStream input = null;
- try {
- input = new FileInputStream(in);
- byte[] buf = new byte[1024];
- int count;
- while (0 < (count = input.read(buf, 0, buf.length))) {
- out.write(buf, 0, count);
- }
- } finally {
- if (null != input) input.close();
- }
- }
-
-
- public static void returnTempDir(File dir) {
- deleteDirectory(dir);
- }
-
- /** @return true if path ends with gif, properties, jpg */
- public static boolean isResourcePath(String path) {
- if (null == path) return false;
- path = path.toLowerCase();
- return (path.endsWith(".gif")
- || path.endsWith(".properties")
- || path.endsWith(".jpg")
- || path.endsWith(".jpeg")
- || path.endsWith(".props")
- );
- }
-
- public static void render(Throwable t, StringBuffer err) { // todo: move
- String name = t.getClass().getName();
- int loc = name.lastIndexOf(".");
- name = name.substring(1+loc);
- err.append(name + ": " + t.getMessage() + "\n"); // todo
- StringWriter sw = new StringWriter();
- t.printStackTrace(new PrintWriter(sw));
- err.append(sw.toString());
- }
-
- private static boolean report(StringBuffer err, String context, String status,
- Throwable throwable) {
- boolean failed = ((null != status) || (null != throwable));
- if ((null != err) && (failed)) {
- if (null != context) {
- err.append(context);
- }
- if (null != status) {
- err.append(status);
- }
- if (null != throwable) {
- render(throwable, err);
- }
- }
- return failed;
- }
-
- /**
- * Copy file.
- * @param src the File to copy - must exist
- * @param dest the File for the target file or directory (will not create directories)
- * @param err the StringBuffer for returning any errors - may be null
- **/
- public static boolean copyFile(File src, File dest, StringBuffer err) {
- boolean result = false;
- String label = "start";
- Throwable throwable = null;
- try {
- if (!ValidFileFilter.FILE_EXISTS.accept(src)) {
- label = "src file does not exist";
- } else {
- if (dest.isDirectory()) {
- dest = new File(dest, src.getName());
- }
- if (ValidFileFilter.FILE_EXISTS.accept(dest)) {
- label = "dest file exists";
- }
- boolean closeWhenDone = true;
- result = copy(new FileInputStream(src),
- new FileOutputStream(dest),
- closeWhenDone);
- }
- label = null;
- } catch (Throwable t) {
- throwable = t;
- }
- String context = "FileUtil.copyFile(src, dest, err)";
- boolean report = report(err, context, label, throwable);
- return (result && !report);
- }
-
- /**
- * Copy URL to file.
- * @param src the URL to copy - must exist
- * @param dest the File for the target file or directory (will not create directories)
- * @param err the StringBuffer for returning any errors - may be null
- **/
- public static boolean copyURL(URL url, File dest, StringBuffer err) { // todo untested.
- boolean result = false;
- String label = "start";
- Throwable throwable = null;
- try {
- if (dest.isDirectory()) {
- String filename = url.getFile();
- if ((null == filename) || (0 == filename.length())) {
- filename = DEFAULT_URL_FILENAME;
- }
- dest = new File(dest, filename);
- }
- if (ValidFileFilter.FILE_EXISTS.accept(dest)) {
- label = "dest file exists";
- }
- boolean closeWhenDone = true;
- result = copy(url.openConnection().getInputStream(),
- new FileOutputStream(dest),
- closeWhenDone);
- label = null;
- } catch (Throwable t) {
- throwable = t;
- }
- String context = "FileUtil.copyURL(src, dest, err)"; // add actual parm to labels?
- boolean report = report(err, context, label, throwable);
- return (result && report);
- }
-
- /**
- * Copy input to output - does not close either
- * @param src the InputStream to copy - must exist
- * @param dest the OutputStream for the target
- * @param close if true, close when done
- */
- public static boolean copy(InputStream src, OutputStream dest,
- boolean close)
- throws IOException {
- boolean result = false;
- IOException throwable = null;
- try {
- byte[] buf = new byte[8*1024];
- int count;
- while (0 < (count = src.read(buf, 0, buf.length))) {
- dest.write(buf, 0, count);
- }
- result = true;
- } catch (IOException t) {
- throwable = t;
- } finally {
- if (close) {
- try { if (null != src) src.close(); }
- catch (IOException e) {
- if (null == throwable) { throwable = e; }
- }
- try { if (null != dest) dest.close(); }
- catch (IOException i) {
- if (null == throwable) { throwable = i; }
- }
- }
- }
- if (null != throwable) throw throwable;
- return result;
- }
-
- /**
- * @return true if dir was an existing directory that is now deleted
- */
- protected static boolean deleteDirectory(File dir) {
- return ((null != dir)
- && dir.exists()
- && dir.isDirectory()
- && FileUtil.descendFileTree(dir, DELETE_FILES, false)
- && FileUtil.descendFileTree(dir, DELETE_DIRS, true)
- && dir.delete());
- }
-
- public static String[] getPaths(File[] files) { // util
- String[] result = new String[files.length];
- for (int i = 0; i < result.length; i++) {
- result[i] = files[i].getPath(); // preserves absolute?
- }
- return result;
- }
-
- //-------- first-order, input and visible interface
-
- protected static final FileFilter DELETE_DIRS = new FileFilter() {
- public boolean accept(File file) {
- return ((null != file) && file.isDirectory()
- && file.exists() && file.delete());
- }
- };
- protected static final FileFilter DELETE_FILES = new FileFilter() {
- public boolean accept(File file) {
- return ((null != file) && !file.isDirectory()
- && file.exists() && file.delete());
- }
- };
-
- } // class FileUtil
-
- /**
- * Localize FileUtil log/signals for now
- * ordinary signals are ignored,
- * but exceptions are printed to err
- * and errors are thrown as Error
- */
- class Log {
- /** ordinary logging - may be suppressed */
- public static final void signal(String s) {
- //System.err.println(s);
- }
- /** print stack trace to System.err */
- public static final void signal(Throwable t, String s) {
- System.err.println(s);
- t.printStackTrace(System.err);
- }
- /** @throws Error(s) always */
- public static final void error(String s) {
- throw new Error(s);
- }
- }
-
- /** read each file out to the zip file */
- class ZipAccumulator implements FileFilter {
- final File parentDir;
- final ZipOutputStream out;
- final FileFilter filter;
- public ZipAccumulator(File parentDir, ZipOutputStream out,
- FileFilter filter) {
- this.parentDir = parentDir;
- this.out = out;
- this.filter = filter;
- }
- public boolean accept(File f) {
- if ((null != filter) && (!filter.accept(f))) {
- return false;
- }
- try {
- FileUtil.addFileToZip(f, parentDir, out);
- return true;
- } catch (IOException e) {
- e.printStackTrace(System.err); // todo
- }
- return false;
- }
- }
|