0001: /*
0002: * Copyright (c) 2007, intarsys consulting GmbH
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * - Redistributions of source code must retain the above copyright notice,
0008: * this list of conditions and the following disclaimer.
0009: *
0010: * - Redistributions in binary form must reproduce the above copyright notice,
0011: * this list of conditions and the following disclaimer in the documentation
0012: * and/or other materials provided with the distribution.
0013: *
0014: * - Neither the name of intarsys nor the names of its contributors may be used
0015: * to endorse or promote products derived from this software without specific
0016: * prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
0022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
0023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
0024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
0026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0028: * POSSIBILITY OF SUCH DAMAGE.
0029: */
0030: package de.intarsys.tools.file;
0031:
0032: import java.io.File;
0033: import java.io.FileInputStream;
0034: import java.io.FileOutputStream;
0035: import java.io.FileWriter;
0036: import java.io.IOException;
0037: import java.io.InputStream;
0038: import java.io.Writer;
0039: import java.net.URL;
0040: import java.util.ArrayList;
0041: import java.util.List;
0042: import de.intarsys.tools.stream.StreamTools;
0043: import de.intarsys.tools.string.StringTools;
0044:
0045: /**
0046: * Some utility methods to ease life with File's
0047: */
0048: public class FileTools {
0049: //
0050: public static final int MAX_BUFFER = 10000;
0051:
0052: /**
0053: * Concatenate the two files given in <code>source</code> and
0054: * <code>destination</code>.
0055: *
0056: * @param source
0057: * The file to be appended.
0058: * @param destination
0059: * The file to append to.
0060: *
0061: * @throws IOException
0062: */
0063: public static void appendFile(File source, File destination)
0064: throws IOException {
0065: FileInputStream is = null;
0066: FileOutputStream os = null;
0067: if (fileTheSame(source, destination)) {
0068: return;
0069: }
0070: try {
0071: is = new FileInputStream(source);
0072: os = new FileOutputStream(destination.getAbsolutePath(),
0073: true);
0074: // todo use StreamTools
0075: byte[] b = new byte[MAX_BUFFER];
0076: for (int i = is.read(b); i != -1;) {
0077: os.write(b, 0, i);
0078: i = is.read(b);
0079: }
0080: } catch (Exception e) {
0081: throw new IOException("copying failed (" + e.getMessage()
0082: + ")");
0083: } finally {
0084: try {
0085: if (is != null) {
0086: is.close();
0087: }
0088: } catch (Exception ignore) {
0089: // ignore failure
0090: }
0091: try {
0092: if (os != null) {
0093: os.close();
0094: }
0095: } catch (Exception ignore) {
0096: // ignore failure
0097: }
0098: }
0099: }
0100:
0101: /**
0102: * Utility method for checking the availablity of a directory.
0103: *
0104: * @param dir
0105: * The directory to check.
0106: * @param create
0107: * Flag if we should create if dir not already exists.
0108: * @param checkCanRead
0109: * Flag if we should check read permission.
0110: * @param checkCanWrite
0111: * Flag if we should check write permission.
0112: *
0113: * @return The checked directory.
0114: *
0115: * @throws IOException
0116: */
0117: public static File checkDirectory(File dir, boolean create,
0118: boolean checkCanRead, boolean checkCanWrite)
0119: throws IOException {
0120: if (dir == null) {
0121: return dir;
0122: }
0123: if (!dir.exists() && create) {
0124: if (!dir.mkdirs()) {
0125: throw new IOException("Can't create directory "
0126: + dir.getPath());
0127: }
0128: }
0129: if (!dir.exists()) {
0130: throw new IOException("Can't create directory "
0131: + dir.getPath());
0132: }
0133: if (!dir.isDirectory()) {
0134: throw new IOException("Can't create directory "
0135: + dir.getPath());
0136: }
0137: if (checkCanRead && !dir.canRead()) {
0138: throw new IOException("No read access for directory "
0139: + dir.getPath());
0140: }
0141: if (checkCanWrite && !dir.canWrite()) {
0142: throw new IOException("No write access for directory "
0143: + dir.getPath());
0144: }
0145: return dir;
0146: }
0147:
0148: /**
0149: * @see #checkDirectory(File, boolean, boolean, boolean)
0150: */
0151: public static File checkDirectory(String path, boolean create,
0152: boolean checkCanRead, boolean checkCanWrite)
0153: throws IOException {
0154: File file = new File(path);
0155: return checkDirectory(file, create, checkCanRead, checkCanWrite);
0156: }
0157:
0158: /**
0159: * Copy the byte content of <code>source</code> to
0160: * <code>destination</code>.
0161: *
0162: * @param source
0163: * The file whose contents we should copy.
0164: * @param destination
0165: * The file where the contents are copied to.
0166: *
0167: * @throws IOException
0168: */
0169: public static void copyBinaryFile(File source, File destination)
0170: throws IOException {
0171: // todo move to stream
0172: if (!destination.getParentFile().exists()) {
0173: destination.getParentFile().mkdirs();
0174: }
0175: FileInputStream is = null;
0176: FileOutputStream os = null;
0177: try {
0178: is = new FileInputStream(source);
0179: os = new FileOutputStream(destination);
0180: StreamTools.copyStream(is, os);
0181: } finally {
0182: StreamTools.close(is);
0183: StreamTools.close(os);
0184: }
0185: destination.setLastModified(source.lastModified());
0186: }
0187:
0188: /**
0189: * @see #copyBinaryFile(File, File)
0190: */
0191: public static void copyFile(File source, File destination)
0192: throws IOException {
0193: copyBinaryFile(source, destination);
0194: }
0195:
0196: /**
0197: * Copy the character content of <code>source</code> to
0198: * <code>destination</code>.
0199: *
0200: * @param source
0201: * The file whose contents we should copy.
0202: * @param sourceEncoding
0203: * The encoding of the source byte stream.
0204: * @param destination
0205: * The file where the contents are copied to.
0206: * @param destinationEncoding
0207: * The encoding of the destination byte stream.
0208: *
0209: * @throws IOException
0210: */
0211: public static void copyFile(File source, String sourceEncoding,
0212: File destination, String destinationEncoding)
0213: throws IOException {
0214: // todo move to stream
0215: if ((sourceEncoding == null) || (destinationEncoding == null)
0216: || sourceEncoding.equals(destinationEncoding)) {
0217: copyBinaryFile(source, destination);
0218: return;
0219: }
0220:
0221: if (!destination.getParentFile().exists()) {
0222: destination.getParentFile().mkdirs();
0223: }
0224: FileInputStream is = null;
0225: FileOutputStream os = null;
0226: try {
0227: is = new FileInputStream(source);
0228: os = new FileOutputStream(destination);
0229: StreamTools.copyEncodedStream(is, sourceEncoding, os,
0230: destinationEncoding);
0231: } catch (Exception e) {
0232: throw new IOException("copying failed (" + e.getMessage()
0233: + ")");
0234: } finally {
0235: StreamTools.close(is);
0236: StreamTools.close(os);
0237: }
0238: }
0239:
0240: public static void copyRecursively(File source, File destination)
0241: throws IOException {
0242: if (source.isFile()) {
0243: copyFile(source, destination);
0244: return;
0245: }
0246: if (!source.isDirectory()) {
0247: throw new IOException("file '" + source.getAbsolutePath()
0248: + "' does not exist.");
0249: }
0250: if (destination.isFile()) {
0251: throw new IOException("cannot copy directory into file");
0252: }
0253: destination.mkdirs();
0254: String[] content = source.list();
0255: for (int i = 0; i < content.length; i++) {
0256: copyRecursively(new File(source, content[i]), new File(
0257: destination, content[i]));
0258: }
0259: }
0260:
0261: public static File copyRecursivelyInto(File source,
0262: File destinationParent, String newName) throws IOException {
0263: if (destinationParent.isFile()) {
0264: throw new IOException("can't copy into file");
0265: }
0266: String destinationName;
0267: if (newName == null) {
0268: destinationName = source.getName();
0269: } else {
0270: destinationName = newName;
0271: }
0272: File destinationFile = new File(destinationParent,
0273: destinationName);
0274: if (source.equals(destinationFile)) {
0275: return destinationFile;
0276: }
0277: if (source.isFile()) {
0278: copyFile(source, destinationFile);
0279: return destinationFile;
0280: }
0281: if (!source.isDirectory()) {
0282: throw new IOException("file '" + source.getAbsolutePath()
0283: + "' does not exist.");
0284: }
0285: String[] content = source.list();
0286: // play safe - list before creating directory (no recursion)
0287: destinationFile.mkdirs();
0288: for (int i = 0; i < content.length; i++) {
0289: copyRecursivelyInto(new File(source, content[i]),
0290: destinationFile, content[i]);
0291: }
0292: return destinationFile;
0293: }
0294:
0295: /**
0296: * Create a file object representing a temporary file in the user's temp dir
0297: * with the same name as the given file.
0298: *
0299: * @param file
0300: * filename to use
0301: * @returns file object representing a temporary file
0302: */
0303: public static File createTempFile(File file) throws IOException {
0304: String name;
0305: String extension;
0306: int index;
0307:
0308: name = file.getName();
0309: index = name.lastIndexOf('.');
0310: if (index >= 0) {
0311: extension = name.substring(index);
0312: name = name.substring(0, index);
0313: } else {
0314: extension = StringTools.EMPTY;
0315: }
0316: if (name.length() < 3) {
0317: name = "tmp" + name;
0318: }
0319: return File.createTempFile(name, extension);
0320: }
0321:
0322: /**
0323: * Create a file object representing a temporary file in the user's temp dir
0324: * with the given filename. Does not actually create a file in the file
0325: * system.
0326: *
0327: * @param filename
0328: * filename to use
0329: * @returns file object representing a temporary file
0330: */
0331: public static File createTempFile(String fileName)
0332: throws IOException {
0333: return createTempFile(new File(fileName));
0334: }
0335:
0336: /**
0337: * Delete any file in <code>directory</code> that is older than
0338: * <code>millis</code> milliseconds. When <code>recursiveScan</code> is
0339: * <code>true</code> the directory lookup is made recursive.
0340: *
0341: * @param directory
0342: * The directory to scan.
0343: * @param millis
0344: * The number of milliseconds a file is allowed to live.
0345: * @param recursiveScan
0346: * Flag if we should handle directories recursive.
0347: *
0348: * @throws IOException
0349: */
0350: public static void deleteAfter(File directory, long millis,
0351: boolean recursiveScan) throws IOException {
0352: if (millis <= 0) {
0353: return;
0354: }
0355:
0356: String[] fileNames = directory.list();
0357: if (fileNames == null) {
0358: throw new IOException("can not list " + directory);
0359: }
0360:
0361: long checkMillis = System.currentTimeMillis() - millis;
0362: for (int j = 0; j < fileNames.length; j++) {
0363: File file = new File(directory, fileNames[j]);
0364: if (file.isDirectory() && recursiveScan) {
0365: deleteAfter(file, millis, recursiveScan);
0366: }
0367: if (file.lastModified() < checkMillis) {
0368: file.delete();
0369: }
0370: }
0371: }
0372:
0373: /**
0374: * Deletes a file or directory, if necessary recursivly.
0375: *
0376: * <p>
0377: * Returns <code>true</code> if file could be deleted inclusive its
0378: * components, otherwise false.
0379: * </p>
0380: *
0381: * @param file
0382: * The file or directory to delete.
0383: *
0384: * @return <code>true</code> if file could be deleted inclusive its
0385: * components, otherwise false.
0386: */
0387: public static boolean deleteRecursivly(File file) {
0388: return deleteRecursivly(file, true);
0389: }
0390:
0391: /**
0392: * Deletes a file or directory, if necessary recursivly.
0393: *
0394: * <p>
0395: * Returns <code>true</code> if file could be deleted inclusive its
0396: * components, otherwise false.
0397: * </p>
0398: *
0399: * @param file
0400: * The file or directory to delete.
0401: * @param deleteRoot
0402: * Flag if the root directory should be deleted itself.
0403: *
0404: * @return <code>true</code> if file could be deleted inclusive its
0405: * components, otherwise false.
0406: */
0407: public static boolean deleteRecursivly(File file, boolean deleteRoot) {
0408: if (!file.exists()) {
0409: return true;
0410: }
0411: if (file.isFile()) {
0412: return file.delete();
0413: }
0414: String[] files = file.list();
0415: if (files == null) {
0416: return false;
0417: }
0418: if (files.length == 0) {
0419: return file.delete();
0420: }
0421: for (int i = 0; i < files.length; i++) {
0422: if (!deleteRecursivly(new File(file, files[i]))) {
0423: return false;
0424: }
0425: }
0426: if (deleteRoot) {
0427: return file.delete();
0428: }
0429: return true;
0430: }
0431:
0432: /**
0433: * Archive a files content.
0434: *
0435: * <p>
0436: * The method creates a copy in the archive directory with a unique name
0437: * that is guaranteed to create a sortable representation so that newer
0438: * files have a "greater" filename. Creation of file names is thread safe.
0439: * If more than <code>max</code> files are in the archive directory, the
0440: * oldest files are deleted. max = 0 means never create archive, max = -1
0441: * means always create archive. If <code>deleteSource</code> is
0442: * <code>true</code>, the file to be archived is deleted after the
0443: * archive was created.
0444: * </p>
0445: *
0446: * @param file
0447: * The file to archive.
0448: * @param root
0449: * The root for relative addressing.
0450: * @param relativePath
0451: * The path relative to root where to create the archive.
0452: * @param max
0453: * The maximum number of archive files allowed.han
0454: * @param sourceEncoding
0455: * The encoding of the file to be archived.
0456: * @param destinationEncoding
0457: * The encoding of the archived file.
0458: * @param deleteSource
0459: * Flag if source should be deleted.
0460: * @param forceArchive
0461: * Flag if we should archive even if file is already in the
0462: * archive directory.
0463: *
0464: * @return The name of the archived file.
0465: *
0466: * @throws IOException
0467: */
0468: public static String dumpArchive(File file, Filename root,
0469: String relativePath, int max, String sourceEncoding,
0470: String destinationEncoding, boolean deleteSource,
0471: boolean forceArchive) throws IOException {
0472: if ((max == 0) || (root == null) || root.isNull()) {
0473: // no archiving desired
0474: return null;
0475: }
0476:
0477: String dirName = root.getDescendent(relativePath)
0478: .getFilenameAbsolute();
0479: File archive;
0480: if (!forceArchive
0481: && dirName.equals(file.getParentFile()
0482: .getAbsolutePath())) {
0483: // i'm already in the temp directory, no need to copy
0484: archive = file;
0485: } else {
0486: DumpDirectory d = DumpDirectory.get(dirName);
0487: archive = d.getDumpFile(file.getName(), max);
0488: if (deleteSource) {
0489: FileTools.renameFile(file, sourceEncoding, archive,
0490: destinationEncoding);
0491: } else {
0492: FileTools.copyFile(file, sourceEncoding, archive,
0493: destinationEncoding);
0494: }
0495: }
0496: try {
0497: archive.setLastModified(System.currentTimeMillis());
0498: } catch (Exception ignore) {
0499: // getLog().logWarning(getLogPrefix() + " could not set modification
0500: // time for file " + renamedFile.getPath());
0501: }
0502: return archive.getAbsolutePath();
0503: }
0504:
0505: /**
0506: * @see #dumpArchive(File, Filename, String, int, String, String, boolean,
0507: * boolean)
0508: */
0509: public static String dumpArchive(InputStream is, Filename root,
0510: String relativePath, int max, String sourceEncoding,
0511: String destinationEncoding, String name) throws IOException {
0512: if ((max == 0) || (root == null) || root.isNull()) {
0513: // no archiving desired
0514: return null;
0515: }
0516:
0517: String dirName = root.getDescendent(relativePath)
0518: .getFilenameAbsolute();
0519: DumpDirectory d = DumpDirectory.get(dirName);
0520: File archive = d.getDumpFile(name, max);
0521: FileOutputStream os = new FileOutputStream(archive);
0522: try {
0523: StreamTools.copyStream(is, os);
0524: } catch (Exception e) {
0525: throw new IOException("archiving failed (" + e.getMessage()
0526: + ")");
0527: } finally {
0528: try {
0529: if (os != null) {
0530: os.close();
0531: }
0532: } catch (Exception ignore) {
0533: // ignore failure
0534: }
0535: }
0536: return archive.getAbsolutePath();
0537: }
0538:
0539: /**
0540: * Create an empty file.
0541: *
0542: * @param file
0543: * @throws IOException
0544: */
0545: public static void emptyFile(File file) throws IOException {
0546: FileOutputStream os = new FileOutputStream(file);
0547: try {
0548: //
0549: } finally {
0550: StreamTools.close(os);
0551: }
0552: }
0553:
0554: /**
0555: * <code>true</code> when the two files represent the same physical file
0556: * in the file system.
0557: *
0558: * @param source
0559: * The first file to be checked.
0560: * @param destination
0561: * The second file to be checked.
0562: *
0563: * @return <code>true</code> when the two files represent the same
0564: * physical file in the file system.
0565: */
0566: public static boolean fileTheSame(File source, File destination) {
0567: try {
0568: if (System.getProperty("os.name").toLowerCase().startsWith(
0569: "win")) {
0570: return source.getCanonicalPath().equalsIgnoreCase(
0571: destination.getCanonicalPath());
0572: } else {
0573: return source.getCanonicalPath().equals(
0574: destination.getCanonicalPath());
0575: }
0576: } catch (IOException e) {
0577: return false;
0578: }
0579: }
0580:
0581: /**
0582: * Create a file from the byte content.
0583: *
0584: * @param file
0585: * The file to write/create
0586: * @param text
0587: * The text to be written into the file.
0588: *
0589: * @throws IOException
0590: */
0591: public static void fromBytes(File file, byte[] bytes)
0592: throws IOException {
0593: // todo change name
0594: FileOutputStream os = new FileOutputStream(file);
0595: try {
0596: // write stream all at once
0597: os.write(bytes);
0598: } finally {
0599: StreamTools.close(os);
0600: }
0601: }
0602:
0603: /**
0604: * Create a file from the string content.
0605: *
0606: * @param file
0607: * The file to write/create
0608: * @param text
0609: * The text to be written into the file.
0610: *
0611: * @throws IOException
0612: */
0613: public static void fromString(File file, String text)
0614: throws IOException {
0615: // todo change name
0616: // todo support encoding
0617: Writer writer = new FileWriter(file);
0618: try {
0619: // write stream all at once
0620: writer.write(text);
0621: } finally {
0622: writer.close();
0623: }
0624: }
0625:
0626: /**
0627: * Get the local name of the file in its directory without the extension.
0628: *
0629: * @param file
0630: * The file whose base name is requested.
0631: *
0632: * @return The local name of the file in its directory without the
0633: * extension.
0634: */
0635: public static String getBaseName(File file) {
0636: if (file == null) {
0637: return getBaseName((String) null, StringTools.EMPTY);
0638: } else {
0639: return getBaseName(file.getName(), StringTools.EMPTY);
0640: }
0641: }
0642:
0643: /**
0644: * Get the local name of the file in its directory without the extension.
0645: *
0646: * @param file
0647: * The file whose base name is requested.
0648: *
0649: * @return The local name of the file in its directory without the
0650: * extension.
0651: */
0652: public static String getBaseName(File file, String defaultName) {
0653: if (file == null) {
0654: return getBaseName((String) null, defaultName);
0655: } else {
0656: return getBaseName(file.getName(), defaultName);
0657: }
0658: }
0659:
0660: /**
0661: * Get the local name of the file in its directory without the extension.
0662: *
0663: * @param filename
0664: * The filename whose base name is requested.
0665: *
0666: * @return The local name of the file in its directory without the
0667: * extension.
0668: */
0669: public static String getBaseName(String filename) {
0670: return getBaseName(filename, StringTools.EMPTY);
0671: }
0672:
0673: /**
0674: * Get the local name of the file in its directory without the extension.
0675: *
0676: * @param filename
0677: * The filename whose base name is requested.
0678: * @param defaultName
0679: * returned if filename is null or a empty String
0680: *
0681: * @return The local name of the file in its directory without the
0682: * extension.
0683: */
0684: public static String getBaseName(String filename, String defaultName) {
0685: if (StringTools.isEmpty(filename)) {
0686: return defaultName;
0687: }
0688: int dotPos = filename.lastIndexOf('.');
0689: if (dotPos >= 1) {
0690: return filename.substring(0, dotPos);
0691: }
0692: return filename;
0693: }
0694:
0695: /**
0696: * Get the extension of the file name. If no extension is present, the empty
0697: * string is returned.
0698: *
0699: * @param file
0700: * The file whose extension is requested.
0701: *
0702: * @return The extension of the file name. If no extension is present, the
0703: * empty string is returned.
0704: */
0705: public static String getExtension(File file) {
0706: return getExtension(file.getName());
0707: }
0708:
0709: /**
0710: * Get the extension of the file name. If no extension is present, the empty
0711: * string is returned.
0712: *
0713: * @param filename
0714: * The filename whose extension is requested.
0715: *
0716: * @return The extension of the file name. If no extension is present, the
0717: * empty string is returned.
0718: */
0719: public static String getExtension(String filename) {
0720: return getExtension(filename, StringTools.EMPTY);
0721: }
0722:
0723: /**
0724: * Get the extension of the file name. If no extension is present, the
0725: * defaultName is returned.
0726: *
0727: * @param filename
0728: * The filename whose extension is requested.
0729: *
0730: * @param defaultName
0731: * returned if the filename is empty or null or there is no
0732: * extension
0733: *
0734: * @return The extension of the file name. If no extension is present, the
0735: * empty string is returned.
0736: */
0737: public static String getExtension(String filename,
0738: String defaultName) {
0739: if (StringTools.isEmpty(filename)) {
0740: return defaultName;
0741: }
0742: int dotPos = filename.lastIndexOf('.');
0743: if (dotPos >= 0) {
0744: return filename.substring(dotPos + 1);
0745: }
0746: return defaultName;
0747: }
0748:
0749: /**
0750: * Return the list of all files in the directory specified by
0751: * <code>path</code>.
0752: *
0753: * @param path
0754: * The name of the directory to lookup.
0755: *
0756: * @return The list of all files in the directory specified by
0757: * <code>path</code>.
0758: */
0759: public static List getFiles(String path) {
0760: return getFiles(path, null);
0761: }
0762:
0763: /**
0764: * Return the list of all files in the directory specified by
0765: * <code>path</code> that match the pattern defined in
0766: * <code>pattern</code>.
0767: *
0768: * @param path
0769: * The name of the directory to lookup.
0770: * @param pattern
0771: * The pattern that should be matched.
0772: *
0773: * @return the list of all files in the directory specified by
0774: * <code>path</code> that match the pattern defined in
0775: * <code>pattern</code>.
0776: */
0777: public static List getFiles(String path, String pattern) {
0778: // todo handle relative file names.
0779: List result = new ArrayList();
0780: WildcardMatch test = new WildcardMatch();
0781: File file = new File(path);
0782:
0783: if (pattern == null) {
0784: pattern = "*";
0785: }
0786:
0787: if (file.isFile() == true) {
0788: Filename tmp = new Filename(path);
0789: if (test.match(pattern, file.getName()) == true) {
0790: result.add(tmp);
0791: }
0792: } else {
0793: String[] allFiles = file.list();
0794: if (allFiles != null) {
0795: for (int i = 0; i < allFiles.length; i++) {
0796: if (test.match(pattern, allFiles[i]) == true) {
0797: Filename tmp = new Filename(path
0798: + File.separator + allFiles[i]);
0799: result.add(tmp);
0800: }
0801: }
0802: }
0803: }
0804: return result;
0805: }
0806:
0807: /**
0808: * Return <code>segment</code> as a path that is interpreted relative to
0809: * <code>root</code> if it specifies a relative address, or a correct
0810: * absolute path.
0811: *
0812: * @param root
0813: * The optional parent of <code>segment</code>.
0814: * @param segment
0815: * The adress whose adress relative to <code>root</code> is
0816: * requested.
0817: *
0818: * @return <code>segment</code> as a path that is interpreted relative to
0819: * <code>root</code> if it specifies a relative address, or a
0820: * correct absolute path.
0821: */
0822: public static String getPathRelative(String root, String segment) {
0823: if (StringTools.isEmpty(segment)) {
0824: return (root == null) ? StringTools.EMPTY : root;
0825: }
0826: if (StringTools.isEmpty(root)) {
0827: return segment;
0828: }
0829:
0830: File file = new File(segment);
0831: if (file.isAbsolute()) {
0832: return segment;
0833: }
0834: File parent = new File(root);
0835: if ((segment.charAt(0) == '/') || (segment.charAt(0) == '\\')) {
0836: // windows platform special; segment is interpreted relative to
0837: // drive
0838: return (new File(getRootName(parent), segment))
0839: .getAbsolutePath();
0840: }
0841:
0842: // segment is relative to directory
0843: return (new File(parent, segment)).getAbsolutePath();
0844: }
0845:
0846: public static String getRelativePath(File sourceFolder, File target)
0847: throws IOException {
0848: return RelativePath.getRelativePath(sourceFolder, target);
0849: }
0850:
0851: /**
0852: * Get the file system root of the file.
0853: *
0854: * <p>
0855: * The root is defined here as a drive or UNC path specification.
0856: * </p>
0857: *
0858: * @param file
0859: * The file whose root is requested.
0860: *
0861: * @return The file system root of the file.
0862: */
0863: public static String getRootName(File file) {
0864: String path = file.getPath();
0865: if (path.length() < 2) {
0866: return StringTools.EMPTY;
0867: }
0868: if (path.charAt(1) == ':') {
0869: return path.substring(0, 2);
0870: }
0871: if ((path.charAt(0) == '\\') && (path.charAt(1) == '\\')) {
0872: return path.substring(0, path.indexOf('\\', 2));
0873: }
0874: return StringTools.EMPTY;
0875: }
0876:
0877: public static String getWindowsStylePath(String osIndependentPath) {
0878: String windows = osIndependentPath.replaceAll("/", "\\\\");
0879: if (windows.startsWith("\\")) {
0880: if (!windows.startsWith("\\\\")) {
0881: windows = windows.substring(1);
0882: int index = windows.indexOf("\\");
0883: if (index < 0) {
0884: index = windows.length();
0885: }
0886: windows = windows.substring(0, index) + ":"
0887: + windows.substring(index);
0888: }
0889: }
0890: return windows;
0891: }
0892:
0893: /**
0894: * The first index of <code>searchstring</code> within <code>file</code>.
0895: *
0896: * <p>
0897: * Use with care!
0898: * </p>
0899: *
0900: * @param file
0901: * The file where we search the string.
0902: * @param searchstring
0903: * The string to be searched.
0904: *
0905: * @return The first index of <code>searchstring</code> within
0906: * <code>file</code>.
0907: *
0908: * @throws IOException
0909: */
0910: public static long indexOf(File file, String searchstring)
0911: throws IOException {
0912: // todo this is ugly
0913: // todo encoding
0914: InputStream stream = null;
0915: String string = null;
0916: try {
0917: stream = new FileInputStream(file);
0918:
0919: byte[] content = new byte[stream.available()];
0920: stream.read(content, 0, stream.available());
0921: string = new String(content);
0922: } finally {
0923: if (stream != null) {
0924: stream.close();
0925: }
0926: }
0927: return string.indexOf(searchstring);
0928: }
0929:
0930: /**
0931: * Return a list of all lines in <code>file</code> that contain
0932: * <code>searchstring</code>. The lines returned contain every character
0933: * of the line remaining after the end of the occurence of
0934: * <code>searchstring</code>.
0935: *
0936: * <p>
0937: * Use with care!
0938: * </p>
0939: *
0940: * @param file
0941: * The file where we search in.
0942: * @param searchstring
0943: * The string to be searched.
0944: *
0945: * @return A list of all lines in <code>file</code> that contain
0946: * <code>searchstring</code>.
0947: *
0948: * @throws IOException
0949: */
0950: public static List linesContaining(File file, String searchstring)
0951: throws IOException {
0952: // todo encoding
0953: // todo ugly implementation
0954: InputStream stream = null;
0955: String string = null;
0956: List list = new ArrayList();
0957:
0958: try {
0959: stream = new FileInputStream(file);
0960:
0961: byte[] content = new byte[stream.available()];
0962:
0963: // read stream all at once
0964: stream.read(content, 0, stream.available());
0965: string = new String(content);
0966: for (int i = 0; i < string.length();) {
0967: int pos = string.indexOf(searchstring, i);
0968: if (pos == -1) {
0969: break;
0970: }
0971:
0972: int start = pos + searchstring.length();
0973: int stop = string.indexOf(System
0974: .getProperty("line.separator"), start);
0975: if (stop == -1) {
0976: stop = string.length();
0977: }
0978: list.add(string.substring(start, stop));
0979: i = stop + 1;
0980: }
0981: } finally {
0982: if (stream != null) {
0983: stream.close();
0984: }
0985: }
0986: return list;
0987: }
0988:
0989: /**
0990: * Append <code>text</code> to the file <code>destination</code>.
0991: *
0992: * @param destination
0993: * The destination file.
0994: * @param text
0995: * The text to be appended.
0996: *
0997: * @throws IOException
0998: */
0999: public static void logToFile(File destination, String text)
1000: throws IOException {
1001: // todo change name
1002: FileOutputStream os = null;
1003: try {
1004: byte[] b = text.getBytes();
1005: os = new FileOutputStream(destination.getAbsolutePath(),
1006: true);
1007: os.write(b, 0, b.length);
1008: } catch (Exception e) {
1009: throw new IOException("logToFile failed (" + e.getMessage()
1010: + ")");
1011: } finally {
1012: try {
1013: if (os != null) {
1014: os.close();
1015: }
1016: } catch (Exception ignore) {
1017: // ignore failure
1018: }
1019: }
1020: }
1021:
1022: /**
1023: * @see #renameFile(File, String, File, String)
1024: */
1025: public static void renameFile(File source, File destination)
1026: throws IOException {
1027: renameFile(source, null, destination, null);
1028: }
1029:
1030: /**
1031: * "Rename" a file.
1032: *
1033: * <p>
1034: * The effect is that there is a new file <code>destination</code>,
1035: * encoded in <code>destinationEncoding</code>, the old file
1036: * <code>source</code> is deleted.
1037: * </p>
1038: *
1039: * @param source
1040: * The source name of the file.
1041: * @param sourceEncoding
1042: * The encoding of the source file.
1043: * @param destination
1044: * The destination name of the file.
1045: * @param destinationEncoding
1046: * The encoding of the destination file.
1047: *
1048: * @throws IOException
1049: * docme
1050: */
1051: public static void renameFile(File source, String sourceEncoding,
1052: File destination, String destinationEncoding)
1053: throws IOException {
1054: if (source.getCanonicalFile().equals(
1055: destination.getCanonicalFile())) {
1056: return;
1057: }
1058: if (((sourceEncoding != null) && (destinationEncoding != null) && !sourceEncoding
1059: .equals(destinationEncoding))
1060: || !source.renameTo(destination)) {
1061: // try a little harder
1062: // some implementations can't rename over different file system
1063: // platforms
1064: // (e.g. from NT to OS/2)
1065: copyFile(source, sourceEncoding, destination,
1066: destinationEncoding);
1067: if (!source.delete()) {
1068: // undo creation of copy
1069: destination.delete();
1070: throw new IOException("deleting " + source + " failed");
1071: }
1072: }
1073: }
1074:
1075: /**
1076: * Create a byte array with the files content.
1077: *
1078: * @param file
1079: * The file to read.
1080: *
1081: * @return Create a byte array with the files content.
1082: *
1083: * @throws IOException
1084: */
1085: public static byte[] toBytes(File file) throws IOException {
1086: InputStream is = null;
1087: try {
1088: is = new FileInputStream(file);
1089: return StreamTools.toByteArray(is);
1090: } finally {
1091: if (is != null) {
1092: is.close();
1093: }
1094: }
1095: }
1096:
1097: public static String toOSIndependentPath(String osPath) {
1098: String unix = osPath.replaceAll("\\\\", "/");
1099: int index = unix.indexOf(":");
1100: if ((index >= 0) && (index < unix.indexOf("/"))) {
1101: unix = "/" + unix.substring(0, index)
1102: + unix.substring(index + 1);
1103: }
1104: return unix;
1105: }
1106:
1107: public static String toOSPath(String osIndependentPath) {
1108: if (File.separatorChar == '/') {
1109: return osIndependentPath;
1110: }
1111: String windows = osIndependentPath.replaceAll("/", "\\\\");
1112: if (windows.startsWith("\\")) {
1113: if (!windows.startsWith("\\\\")) {
1114: windows = windows.substring(1);
1115: int index = windows.indexOf("\\");
1116: if (index < 0) {
1117: index = windows.length();
1118: }
1119: windows = windows.substring(0, index) + ":"
1120: + windows.substring(index);
1121: }
1122: }
1123: return windows;
1124: }
1125:
1126: /**
1127: * Read a file's content at once and return as a string.
1128: *
1129: * <p>
1130: * Use with care!
1131: * </p>
1132: *
1133: * @param file
1134: * The file to read.
1135: *
1136: * @return The string content of the file.
1137: *
1138: * @throws IOException
1139: */
1140: public static String toString(File file) throws IOException {
1141: return toString(file, System.getProperty("file.encoding"));
1142: }
1143:
1144: /**
1145: * Read a file's content at once and return as a string in the correct
1146: * encoding.
1147: *
1148: * <p>
1149: * Use with care!
1150: * </p>
1151: *
1152: * @param file
1153: * The file to read.
1154: * @param encoding
1155: * The encoding to use.
1156: *
1157: * @return The string content of the file.
1158: *
1159: * @throws IOException
1160: */
1161: public static String toString(File file, String encoding)
1162: throws IOException {
1163: InputStream is = null;
1164: try {
1165: is = new FileInputStream(file);
1166: return StreamTools.toString(is, encoding);
1167: } finally {
1168: if (is != null) {
1169: is.close();
1170: }
1171: }
1172: }
1173:
1174: /**
1175: * Wait for a file to arrive.
1176: *
1177: * <p>
1178: * The method waits at most <code>timeout</code> milliseconds for a file
1179: * to arrive. When <code>delay</code> is != 0 the method checks the file's
1180: * size for changes it reaches a stable size.
1181: * </p>
1182: *
1183: * @param file
1184: * The file to wait for.
1185: * @param timeout
1186: * The maximum time in milliseconds to wait for first occurence
1187: * of <code>file</code>.
1188: * @param delay
1189: * The number of milliseconds between two checks against the
1190: * files size.
1191: *
1192: * @throws IOException
1193: */
1194: public static void wait(File file, long timeout, long delay)
1195: throws IOException {
1196: // todo zero length files
1197: long stop = System.currentTimeMillis() + timeout;
1198: for (;;) {
1199: try {
1200: if (file.exists()) {
1201: if (delay > 0) {
1202: long oldSize = -1;
1203: long newSize = file.length();
1204: for (;;) {
1205: if (oldSize != newSize) {
1206: oldSize = newSize;
1207: Thread.sleep(delay);
1208: newSize = file.length();
1209: continue;
1210: }
1211: break;
1212: }
1213: }
1214: return;
1215: }
1216: if (System.currentTimeMillis() > stop) {
1217: // timeout
1218: throw new IOException("timeout waiting for "
1219: + file.getPath());
1220: }
1221: Thread.sleep(1000);
1222: } catch (InterruptedException e) {
1223: // interrupted
1224: throw new IOException("interrupted waiting for "
1225: + file.getPath());
1226: }
1227: }
1228: }
1229:
1230: static final String[] hex = { "%00", "%01", "%02", "%03", "%04",
1231: "%05", "%06", "%07", "%08", "%09", "%0a", "%0b", "%0c",
1232: "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13", "%14",
1233: "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c",
1234: "%1d", "%1e", "%1f", "%20", "%21", "%22", "%23", "%24",
1235: "%25", "%26", "%27", "%28", "%29", "%2a", "%2b", "%2c",
1236: "%2d", "%2e", "%2f", "%30", "%31", "%32", "%33", "%34",
1237: "%35", "%36", "%37", "%38", "%39", "%3a", "%3b", "%3c",
1238: "%3d", "%3e", "%3f", "%40", "%41", "%42", "%43", "%44",
1239: "%45", "%46", "%47", "%48", "%49", "%4a", "%4b", "%4c",
1240: "%4d", "%4e", "%4f", "%50", "%51", "%52", "%53", "%54",
1241: "%55", "%56", "%57", "%58", "%59", "%5a", "%5b", "%5c",
1242: "%5d", "%5e", "%5f", "%60", "%61", "%62", "%63", "%64",
1243: "%65", "%66", "%67", "%68", "%69", "%6a", "%6b", "%6c",
1244: "%6d", "%6e", "%6f", "%70", "%71", "%72", "%73", "%74",
1245: "%75", "%76", "%77", "%78", "%79", "%7a", "%7b", "%7c",
1246: "%7d", "%7e", "%7f", "%80", "%81", "%82", "%83", "%84",
1247: "%85", "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c",
1248: "%8d", "%8e", "%8f", "%90", "%91", "%92", "%93", "%94",
1249: "%95", "%96", "%97", "%98", "%99", "%9a", "%9b", "%9c",
1250: "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3", "%a4",
1251: "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac",
1252: "%ad", "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4",
1253: "%b5", "%b6", "%b7", "%b8", "%b9", "%ba", "%bb", "%bc",
1254: "%bd", "%be", "%bf", "%c0", "%c1", "%c2", "%c3", "%c4",
1255: "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb", "%cc",
1256: "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4",
1257: "%d5", "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc",
1258: "%dd", "%de", "%df", "%e0", "%e1", "%e2", "%e3", "%e4",
1259: "%e5", "%e6", "%e7", "%e8", "%e9", "%ea", "%eb", "%ec",
1260: "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3", "%f4",
1261: "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc",
1262: "%fd", "%fe", "%ff" };
1263:
1264: private FileTools() {
1265: //
1266: }
1267:
1268: /**
1269: * Try to get a valid parent for file.
1270: *
1271: * @param file
1272: */
1273: public static File getParentFile(File file) {
1274: File parentFile = file.getParentFile();
1275: if (parentFile == null) {
1276: parentFile = file.getAbsoluteFile().getParentFile();
1277: }
1278: if (parentFile == null) {
1279: return null;
1280: }
1281: File grandpa = parentFile.getParentFile();
1282: if (grandpa != null) {
1283: // filter UNC path root also
1284: String grandpaPath = grandpa.getAbsolutePath();
1285: if ((grandpaPath.length() == 2)
1286: && (grandpaPath.charAt(1) == File.separatorChar)) {
1287: return null;
1288: }
1289: }
1290: return parentFile;
1291: }
1292:
1293: /**
1294: * @param param
1295: * java.lang.String Trims and replaces all characters which are
1296: * not allowed in filenames (for Win32) with spaces
1297: *
1298: * @return java.lang.String
1299: */
1300: public static String trimToFileConform(String param) {
1301: return trimToFileConform(param, null);
1302: }
1303:
1304: private static final char FILE_OK_CHAR = '_';
1305:
1306: /**
1307: * docme
1308: *
1309: * @param param
1310: * docme
1311: * @param protectedChars
1312: * docme
1313: *
1314: * @return docme
1315: */
1316: public static String trimToFileConform(String param,
1317: String protectedChars) {
1318: if (param == null) {
1319: return null;
1320: }
1321: String replaceChars = "\n\t\b\f\r\"*?<>|:";
1322: replaceChars = FileTools.replaceAllChars(replaceChars,
1323: protectedChars, FILE_OK_CHAR);
1324: String tmp = param.trim();
1325:
1326: // make for uniform use of slash and backslash
1327: tmp = tmp.replace('\\', File.separatorChar);
1328: tmp = tmp.replace('/', File.separatorChar);
1329: String drivePrefix = StringTools.EMPTY;
1330: if ((tmp.length() > 2) && (tmp.charAt(1) == ':')) {
1331: drivePrefix = tmp.substring(0, 2);
1332: tmp = tmp.substring(2);
1333: }
1334: tmp = FileTools
1335: .replaceAllChars(tmp, replaceChars, FILE_OK_CHAR);
1336: return drivePrefix + tmp;
1337: }
1338:
1339: /**
1340: * docme
1341: *
1342: * @param param
1343: * docme
1344: *
1345: * @return docme
1346: */
1347: public static String trimToURLConform(String param) {
1348: URL url;
1349: if (param == null) {
1350: return null;
1351: }
1352:
1353: // don't be to stringent with slashes
1354: param = param.trim().replace('\\', '/');
1355: try {
1356: url = new URL(param);
1357: } catch (Exception e) {
1358: return null;
1359: }
1360: boolean isFileURL = url.getProtocol().equalsIgnoreCase("file");
1361:
1362: // new URL(String) cuts all leading '/' of file except the first
1363: // --> UNC paths are not recognizable
1364: // therefore UNC paths are detected here
1365: // file:///// is UNC
1366: // file://// is not
1367: boolean isUNC = (url.getProtocol().equalsIgnoreCase("file") && param
1368: .startsWith("//", 8));
1369:
1370: String host = url.getHost();
1371: host = urlPathEscapeEncodeHTTP(host);
1372: String file = url.getFile();
1373: if (file == null) {
1374: return null;
1375: }
1376: if (file.startsWith("/")) {
1377: file = file.substring(1);
1378: }
1379: String path = file;
1380:
1381: String query = StringTools.EMPTY;
1382: int index = file.indexOf('?');
1383: if (index != -1) {
1384: path = file.substring(0, index);
1385: query = file.substring(index + 1);
1386: }
1387:
1388: path = FileTools.trimToFileConform(path);
1389: // for relative path (?)
1390: if ((path.length() > 0)
1391: && (path.charAt(0) != File.separatorChar)) {
1392: path = File.separatorChar + path;
1393: }
1394:
1395: // fill up to file URL to get file:///c:/...
1396: if (isFileURL) {
1397: path = "//" + path;
1398: }
1399:
1400: // fill up to file URL to get file://///ws15/...
1401: // for VA's VM one had to add '//' to get the correct URL
1402: // but for JRE 1.3.1 it works only this way
1403: // todo url handling for files. unit tests
1404: if (isUNC) {
1405: path = "/" + path;
1406: }
1407: path = path.replace('\\', '/');
1408: if (isFileURL) {
1409: path = urlPathEscapeEncodeFILE(path);
1410: } else {
1411: path = urlPathEscapeEncodeHTTP(path);
1412: }
1413:
1414: // treating of queries starting with '?'
1415: query = urlQueryFormEncode(query);
1416: if (query.length() > 0) {
1417: file = path + "?" + query;
1418: } else {
1419: file = path;
1420: }
1421:
1422: // treating of parameters sequences starting with '#'
1423: // known use case is with Acrobat plugin for IE: url#FDF=url
1424: // todo treat # part recursively with this method? only after skipping
1425: // characters FDF=, are there others?
1426: // todo what happens when ? and # occurs?
1427: // do not add it to the file again because '#' would be replaced in
1428: // urlPathEscapeEncode()
1429: String pluginParameter = StringTools.EMPTY;
1430:
1431: // with '#' starting parameter sequence is not part of file!
1432: index = url.toString().indexOf('#');
1433: if (index != -1) {
1434: pluginParameter = url.toString().substring(index + 1);
1435: }
1436: pluginParameter = pluginParameter.replace('\\', '/');
1437: if (isFileURL) {
1438: pluginParameter = urlPathEscapeEncodeFILE(pluginParameter);
1439: } else {
1440: pluginParameter = urlPathEscapeEncodeHTTP(pluginParameter);
1441: }
1442: if (pluginParameter.length() > 0) {
1443: file = file + "#" + pluginParameter;
1444: }
1445:
1446: try {
1447: return (new URL(url.getProtocol(), host, url.getPort(),
1448: file)).toString();
1449: } catch (Exception e) {
1450: return null;
1451: }
1452: }
1453:
1454: /**
1455: * DOCUMENT ME!
1456: *
1457: * @param s
1458: * The string to be encoded
1459: *
1460: * @return The encoded string
1461: */
1462: public static String urlPathEscapeEncodeFILE(String s) {
1463: StringBuilder sbuf = new StringBuilder();
1464: int len = s.length();
1465: for (int i = 0; i < len; i++) {
1466: int ch = s.charAt(i);
1467: if (ch == '<') {
1468: sbuf.append(hex[ch]);
1469: } else if (ch == '>') {
1470: sbuf.append(hex[ch]);
1471: } else if (ch == '"') {
1472: sbuf.append(hex[ch]);
1473: } else if (ch <= 0x001f) {
1474: sbuf.append(hex[ch]);
1475: } else if ((ch > 0x00FF) && (ch <= 0x07FF)) { // non-ASCII <=
1476: // 0x7FF
1477: sbuf.append(hex[0xc0 | (ch >> 6)]);
1478: sbuf.append(hex[0x80 | (ch & 0x3F)]);
1479: } else if (ch > 0x07FF) { // 0x7FF < ch <= 0xFFFF
1480: sbuf.append(hex[0xe0 | (ch >> 12)]);
1481: sbuf.append(hex[0x80 | ((ch >> 6) & 0x3F)]);
1482: sbuf.append(hex[0x80 | (ch & 0x3F)]);
1483: } else {
1484: sbuf.append((char) ch);
1485: }
1486: }
1487: return sbuf.toString();
1488: }
1489:
1490: /**
1491: * DOCUMENT ME!
1492: *
1493: * @param s
1494: * The string to be encoded
1495: *
1496: * @return The encoded string
1497: */
1498: public static String urlPathEscapeEncodeHTTP(String s) {
1499: StringBuilder sbuf = new StringBuilder();
1500: int len = s.length();
1501: for (int i = 0; i < len; i++) {
1502: int ch = s.charAt(i);
1503: if (ch == ' ') {
1504: sbuf.append(hex[ch]);
1505: } else if (ch == '<') {
1506: sbuf.append(hex[ch]);
1507: } else if (ch == '>') {
1508: sbuf.append(hex[ch]);
1509: } else if (ch == '"') {
1510: sbuf.append(hex[ch]);
1511: } else if (ch == '#') {
1512: sbuf.append(hex[ch]);
1513: } else if (ch == '%') {
1514: sbuf.append(hex[ch]);
1515: } else if (ch == '\u20AC') { // EURO SIGN
1516: sbuf.append(hex[128]);
1517: } else if (ch <= 0x001f) {
1518: sbuf.append(hex[ch]);
1519: } else if ((ch > 0x00A0) && (ch <= 0x00FF)) { // ASCII / special
1520: // chars
1521: sbuf.append(hex[ch]);
1522: } else if ((ch > 0x00FF) && (ch <= 0x07FF)) { // non-ASCII <=
1523: // 0x7FF
1524: sbuf.append(hex[0xc0 | (ch >> 6)]);
1525: sbuf.append(hex[0x80 | (ch & 0x3F)]);
1526: } else if (ch > 0x07FF) { // 0x7FF < ch <= 0xFFFF
1527: sbuf.append(hex[0xe0 | (ch >> 12)]);
1528: sbuf.append(hex[0x80 | ((ch >> 6) & 0x3F)]);
1529: sbuf.append(hex[0x80 | (ch & 0x3F)]);
1530: } else {
1531: sbuf.append((char) ch);
1532: }
1533: }
1534: return sbuf.toString();
1535: }
1536:
1537: /**
1538: * Encode a string to the "x-www-form-urlencoded" form, enhanced with the
1539: * UTF-8-in-URL proposal. This is what happens:
1540: *
1541: * <ul>
1542: * <li>
1543: * <p>
1544: * The ASCII characters 'a' through 'z', 'A' through 'Z', and '0' through
1545: * '9' remain the same.
1546: * </p>
1547: * </li>
1548: * <li>
1549: * <p>
1550: * The space character ' ' is converted into a plus sign '+'.
1551: * </p>
1552: * </li>
1553: * <li>
1554: * <p>
1555: * All other ASCII characters are converted into the 3-character string
1556: * "%xy", where xy is the two-digit hexadecimal representation of the
1557: * character code
1558: * </p>
1559: * </li>
1560: * <li>
1561: * <p>
1562: * All non-ASCII characters are encoded in two steps: first to a sequence of
1563: * 2 or 3 bytes, using the UTF-8 algorithm; secondly each of these bytes is
1564: * encoded as "%xx".
1565: * </p>
1566: * </li>
1567: * </ul>
1568: *
1569: *
1570: * @param s
1571: * The string to be encoded
1572: *
1573: * @return The encoded string
1574: */
1575: public static String urlQueryFormEncode(String s) {
1576: StringBuilder sbuf = new StringBuilder();
1577: int len = s.length();
1578: for (int i = 0; i < len; i++) {
1579: int ch = s.charAt(i);
1580: if (('A' <= ch) && (ch <= 'Z')) { // 'A'..'Z'
1581: sbuf.append((char) ch);
1582: } else if (('a' <= ch) && (ch <= 'z')) { // 'a'..'z'
1583: sbuf.append((char) ch);
1584: } else if (('0' <= ch) && (ch <= '9')) { // '0'..'9'
1585: sbuf.append((char) ch);
1586: } else if (ch == ' ') { // space
1587: sbuf.append('+');
1588: } else if (ch <= 0x007f) { // other ASCII
1589: sbuf.append(hex[ch]);
1590: } else if (ch <= 0x07FF) { // non-ASCII <= 0x7FF
1591: sbuf.append(hex[0xc0 | (ch >> 6)]);
1592: sbuf.append(hex[0x80 | (ch & 0x3F)]);
1593: } else { // 0x7FF < ch <= 0xFFFF
1594: sbuf.append(hex[0xe0 | (ch >> 12)]);
1595: sbuf.append(hex[0x80 | ((ch >> 6) & 0x3F)]);
1596: sbuf.append(hex[0x80 | (ch & 0x3F)]);
1597: }
1598: }
1599: return sbuf.toString();
1600: }
1601:
1602: /**
1603: * docme
1604: *
1605: * @param source
1606: * docme
1607: * @param replace
1608: * docme
1609: * @param with
1610: * docme
1611: *
1612: * @return docme
1613: */
1614: public static String replaceAllChars(String source, String replace,
1615: char with) {
1616: if ((source == null) || (replace == null)) {
1617: return source;
1618: }
1619: String result = source;
1620: for (int i = 0; i < replace.length(); i++) {
1621: char newChar = replace.charAt(i);
1622: if (newChar != with) {
1623: result = result.replace(newChar, with);
1624: }
1625: }
1626: return result;
1627: }
1628: }
|