| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| package org.apache.tools.ant.launch; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FilenameFilter; |
| import java.io.UnsupportedEncodingException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.nio.charset.StandardCharsets; |
| import java.text.CharacterIterator; |
| import java.text.StringCharacterIterator; |
| import java.util.Locale; |
| |
| // CheckStyle:LineLengthCheck OFF - urls are long! |
| /** |
| * The Locator is a utility class which is used to find certain items |
| * in the environment. |
| * <p> |
| * It is used at boot time in the launcher, and cannot make use of any of Ant's other classes. |
| * <p> |
| * This is a surprisingly brittle piece of code, and has had lots of bugs filed against it: |
| * <a href="http://issues.apache.org/bugzilla/show_bug.cgi?id=42275">running ant off a network share can cause Ant to fail</a>, |
| * <a href="http://issues.apache.org/bugzilla/show_bug.cgi?id=8031">use File.toURI().toURL().toExternalForm()</a>, |
| * <a href="http://issues.apache.org/bugzilla/show_bug.cgi?id=42222">Locator implementation not encoding URI strings properly: spaces in paths</a>. |
| * It also breaks Eclipse 3.3 Betas: |
| * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=183283">Exception if installation path has spaces</a>. |
| * <p> |
| * Be very careful when making changes to this class, as a break will upset a lot of people. |
| * @since Ant 1.6 |
| */ |
| // CheckStyle:LineLengthCheck ON - urls are long! |
| public final class Locator { |
| |
| private static final int NIBBLE = 4; |
| private static final int NIBBLE_MASK = 0xF; |
| |
| private static final int ASCII_SIZE = 128; |
| |
| private static final int BYTE_SIZE = 256; |
| |
| private static final int WORD = 16; |
| |
| private static final int SPACE = 0x20; |
| private static final int DEL = 0x7F; |
| |
| // stolen from org.apache.xerces.impl.XMLEntityManager#getUserDir() |
| // of the Xerces-J team |
| // which ASCII characters need to be escaped |
| private static boolean[] gNeedEscaping = new boolean[ASCII_SIZE]; |
| // the first hex character if a character needs to be escaped |
| private static char[] gAfterEscaping1 = new char[ASCII_SIZE]; |
| // the second hex character if a character needs to be escaped |
| private static char[] gAfterEscaping2 = new char[ASCII_SIZE]; |
| private static char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7', |
| '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; |
| /** Error string used when an invalid uri is seen */ |
| public static final String ERROR_NOT_FILE_URI |
| = "Can only handle valid file: URIs, not "; |
| |
| // initialize the above 3 arrays |
| static { |
| for (int i = 0; i < SPACE; i++) { |
| gNeedEscaping[i] = true; |
| gAfterEscaping1[i] = gHexChs[i >> NIBBLE]; |
| gAfterEscaping2[i] = gHexChs[i & NIBBLE_MASK]; |
| } |
| gNeedEscaping[DEL] = true; |
| gAfterEscaping1[DEL] = '7'; |
| gAfterEscaping2[DEL] = 'F'; |
| char[] escChs = {' ', '<', '>', '#', '%', '"', '{', '}', |
| '|', '\\', '^', '~', '[', ']', '`'}; |
| int len = escChs.length; |
| char ch; |
| for (int i = 0; i < len; i++) { |
| ch = escChs[i]; |
| gNeedEscaping[ch] = true; |
| gAfterEscaping1[ch] = gHexChs[ch >> NIBBLE]; |
| gAfterEscaping2[ch] = gHexChs[ch & NIBBLE_MASK]; |
| } |
| } |
| /** |
| * Not instantiable |
| */ |
| private Locator() { |
| } |
| |
| /** |
| * Find the directory or jar file the class has been loaded from. |
| * |
| * @param c the class whose location is required. |
| * @return the file or jar with the class or null if we cannot |
| * determine the location. |
| * |
| * @since Ant 1.6 |
| */ |
| public static File getClassSource(Class<?> c) { |
| String classResource = c.getName().replace('.', '/') + ".class"; |
| return getResourceSource(c.getClassLoader(), classResource); |
| } |
| |
| /** |
| * Find the directory or jar a given resource has been loaded from. |
| * |
| * @param c the classloader to be consulted for the source. |
| * @param resource the resource whose location is required. |
| * |
| * @return the file with the resource source or null if |
| * we cannot determine the location. |
| * |
| * @since Ant 1.6 |
| */ |
| public static File getResourceSource(ClassLoader c, String resource) { |
| if (c == null) { |
| c = Locator.class.getClassLoader(); |
| } |
| URL url = null; |
| if (c == null) { |
| url = ClassLoader.getSystemResource(resource); |
| } else { |
| url = c.getResource(resource); |
| } |
| if (url != null) { |
| String u = url.toString(); |
| try { |
| if (u.startsWith("jar:file:")) { |
| return new File(fromJarURI(u)); |
| } else if (u.startsWith("file:")) { |
| int tail = u.indexOf(resource); |
| String dirName = u.substring(0, tail); |
| return new File(fromURI(dirName)); |
| } |
| } catch (IllegalArgumentException e) { |
| //unable to determine the URI for reasons unknown. |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| |
| |
| /** |
| * Constructs a file path from a <code>file:</code> URI. |
| * |
| * <p>Will be an absolute path if the given URI is absolute.</p> |
| * |
| * <p>Prior to Java 1.4,<!-- TODO is JDK version actually relevant? --> |
| * swallows '%' that are not followed by two characters.</p> |
| * |
| * See <a href="http://www.w3.org/TR/xml11/#dt-sysid">dt-sysid</a> |
| * which makes some mention of how |
| * characters not supported by URI Reference syntax should be escaped. |
| * |
| * @param uri the URI designating a file in the local filesystem. |
| * @return the local file system path for the file. |
| * @throws IllegalArgumentException if the URI is malformed or not a legal file: URL |
| * @since Ant 1.6 |
| */ |
| public static String fromURI(String uri) { |
| return fromURIJava13(uri); |
| // #buzilla8031: first try Java 1.4. |
| // TODO should use java.net.URI now that we can rely on 1.4... |
| // but check for UNC-related regressions, e.g. #42275 |
| // (and remember that \\server\share\file -> file:////server/share/file |
| // rather than -> file://server/share/file as it should; |
| // fixed only in JDK 7's java.nio.file.Path.toUri) |
| // return fromUriJava14(uri); |
| } |
| |
| /** |
| * Java1.4+ code to extract the path from the URI. |
| * @param uri |
| * @return null if a conversion was not possible |
| */ |
| /* currently unused: |
| private static String fromUriJava14(String uri) { |
| // Also check for properly formed URIs. Ant formerly recommended using |
| // nonsense URIs such as "file:./foo.xml" in XML includes. You shouldn't |
| // do that (just "foo.xml" is correct) but for compatibility we special-case |
| // things when the path is not absolute, and fall back to the old parsing behavior. |
| if (uri.startsWith("file:/")) { |
| try { |
| File f = new File(URI.create(encodeURI(uri))); |
| //bug #42227 forgot to decode before returning |
| return decodeUri(f.getAbsolutePath()); |
| } catch (IllegalArgumentException e) { |
| // Bad URI, pass this on. |
| // no, this is downgraded to a warning after various |
| // JRE bugs surfaced. Hand off |
| // to our built in code on a failure |
| //throw new IllegalArgumentException( |
| // "Bad URI " + uri + ":" + e.getMessage(), e); |
| e.printStackTrace(); |
| } catch (Exception e) { |
| // Unexpected exception? Should not happen. |
| e.printStackTrace(); |
| } |
| } |
| return null; |
| } |
| */ |
| |
| /** |
| * @param uri uri to expand |
| * @return the decoded URI |
| * @since Ant1.7.1 |
| */ |
| private static String fromURIJava13(String uri) { |
| // Fallback method for Java 1.3 or earlier. |
| |
| URL url = null; |
| try { |
| url = new URL(uri); |
| } catch (MalformedURLException emYouEarlEx) { |
| // Ignore malformed exception |
| } |
| if (url == null || !("file".equals(url.getProtocol()))) { |
| throw new IllegalArgumentException(ERROR_NOT_FILE_URI + uri); |
| } |
| StringBuffer buf = new StringBuffer(url.getHost()); |
| if (buf.length() > 0) { |
| buf.insert(0, File.separatorChar).insert(0, File.separatorChar); |
| } |
| String file = url.getFile(); |
| int queryPos = file.indexOf('?'); |
| buf.append((queryPos < 0) ? file : file.substring(0, queryPos)); |
| |
| uri = buf.toString().replace('/', File.separatorChar); |
| |
| if (File.pathSeparatorChar == ';' && uri.startsWith("\\") && uri.length() > 2 |
| && Character.isLetter(uri.charAt(1)) && uri.lastIndexOf(':') > -1) { |
| uri = uri.substring(1); |
| } |
| String path = null; |
| try { |
| path = decodeUri(uri); |
| //consider adding the current directory. This is not done when |
| //the path is a UNC name |
| String cwd = System.getProperty("user.dir"); |
| int posi = cwd.indexOf(':'); |
| boolean pathStartsWithFileSeparator = path.startsWith(File.separator); |
| boolean pathStartsWithUNC = path.startsWith("" + File.separator + File.separator); |
| if ((posi > 0) && pathStartsWithFileSeparator && !pathStartsWithUNC) { |
| path = cwd.substring(0, posi + 1) + path; |
| } |
| } catch (UnsupportedEncodingException exc) { |
| // not sure whether this is clean, but this method is |
| // declared not to throw exceptions. |
| throw new IllegalStateException( |
| "Could not convert URI " + uri + " to path: " |
| + exc.getMessage()); |
| } |
| return path; |
| } |
| |
| /** |
| * Crack a JAR URI. |
| * This method is public for testing; we may delete it without any warning -it is not part of Ant's stable API. |
| * @param uri uri to expand; contains jar: somewhere in it |
| * @return the decoded URI |
| * @since Ant1.7.1 |
| */ |
| public static String fromJarURI(String uri) { |
| int pling = uri.indexOf("!/"); |
| String jarName = uri.substring("jar:".length(), pling); |
| return fromURI(jarName); |
| } |
| |
| /** |
| * Decodes an Uri with % characters. |
| * The URI is escaped |
| * @param uri String with the uri possibly containing % characters. |
| * @return The decoded Uri |
| * @throws UnsupportedEncodingException if UTF-8 is not available |
| * @since Ant 1.7 |
| */ |
| public static String decodeUri(String uri) throws UnsupportedEncodingException { |
| if (uri.indexOf('%') == -1) { |
| return uri; |
| } |
| ByteArrayOutputStream sb = new ByteArrayOutputStream(uri.length()); |
| CharacterIterator iter = new StringCharacterIterator(uri); |
| for (char c = iter.first(); c != CharacterIterator.DONE; |
| c = iter.next()) { |
| if (c == '%') { |
| char c1 = iter.next(); |
| if (c1 != CharacterIterator.DONE) { |
| int i1 = Character.digit(c1, WORD); |
| char c2 = iter.next(); |
| if (c2 != CharacterIterator.DONE) { |
| int i2 = Character.digit(c2, WORD); |
| sb.write((char) ((i1 << NIBBLE) + i2)); |
| } |
| } |
| } else if (c >= 0x0000 && c < 0x0080) { |
| sb.write(c); |
| } else { // #50543 |
| byte[] bytes = String.valueOf(c).getBytes(StandardCharsets.UTF_8); |
| sb.write(bytes, 0, bytes.length); |
| } |
| } |
| return sb.toString(StandardCharsets.UTF_8.name()); |
| } |
| |
| /** |
| * Encodes an Uri with % characters. |
| * The URI is escaped |
| * @param path String to encode. |
| * @return The encoded string, according to URI norms |
| * @since Ant 1.7 |
| */ |
| public static String encodeURI(String path) { |
| int i = 0; |
| int len = path.length(); |
| int ch = 0; |
| StringBuffer sb = null; |
| for (; i < len; i++) { |
| ch = path.charAt(i); |
| // if it's not an ASCII character, break here, and use UTF-8 encoding |
| if (ch >= ASCII_SIZE) { |
| break; |
| } |
| if (gNeedEscaping[ch]) { |
| if (sb == null) { |
| sb = new StringBuffer(path.substring(0, i)); |
| } |
| sb.append('%'); |
| sb.append(gAfterEscaping1[ch]); |
| sb.append(gAfterEscaping2[ch]); |
| // record the fact that it's escaped |
| } else if (sb != null) { |
| sb.append((char) ch); |
| } |
| } |
| |
| // we saw some non-ascii character |
| if (i < len) { |
| if (sb == null) { |
| sb = new StringBuffer(path.substring(0, i)); |
| } |
| // get UTF-8 bytes for the remaining sub-string |
| byte[] bytes = null; |
| byte b; |
| bytes = path.substring(i).getBytes(StandardCharsets.UTF_8); |
| len = bytes.length; |
| |
| // for each byte |
| for (i = 0; i < len; i++) { |
| b = bytes[i]; |
| // for non-ascii character: make it positive, then escape |
| if (b < 0) { |
| ch = b + BYTE_SIZE; |
| sb.append('%'); |
| sb.append(gHexChs[ch >> NIBBLE]); |
| sb.append(gHexChs[ch & NIBBLE_MASK]); |
| } else if (gNeedEscaping[b]) { |
| sb.append('%'); |
| sb.append(gAfterEscaping1[b]); |
| sb.append(gAfterEscaping2[b]); |
| } else { |
| sb.append((char) b); |
| } |
| } |
| } |
| return sb == null ? path : sb.toString(); |
| } |
| |
| /** |
| * Convert a File to a URL. |
| * File.toURL() does not encode characters like #. |
| * File.toURI() has been introduced in java 1.4, so |
| * Ant cannot use it (except by reflection) <!-- TODO no longer true --> |
| * FileUtils.toURI() cannot be used by Locator.java |
| * Implemented this way. |
| * File.toURL() adds file: and changes '\' to '/' for dos OSes |
| * encodeURI converts characters like ' ' and '#' to %DD |
| * @param file the file to convert |
| * @return URL the converted File |
| * @throws MalformedURLException on error |
| * @deprecated since 1.9, use {@link FileUtils#getFileURL(File)} |
| */ |
| @Deprecated |
| public static URL fileToURL(File file) |
| throws MalformedURLException { |
| return new URL(file.toURI().toASCIIString()); |
| } |
| |
| /** |
| * Get the File necessary to load the Sun compiler tools. If the classes |
| * are available to this class, then no additional URL is required and |
| * null is returned. This may be because the classes are explicitly in the |
| * class path or provided by the JVM directly. |
| * |
| * @return the tools jar as a File if required, null otherwise. |
| */ |
| public static File getToolsJar() { |
| // firstly check if the tools jar is already in the classpath |
| boolean toolsJarAvailable = false; |
| try { |
| // just check whether this throws an exception |
| Class.forName("com.sun.tools.javac.Main"); |
| toolsJarAvailable = true; |
| } catch (Exception e) { |
| try { |
| Class.forName("sun.tools.javac.Main"); |
| toolsJarAvailable = true; |
| } catch (Exception e2) { |
| // ignore |
| } |
| } |
| if (toolsJarAvailable) { |
| return null; |
| } |
| // couldn't find compiler - try to find tools.jar |
| // based on java.home setting |
| String libToolsJar |
| = File.separator + "lib" + File.separator + "tools.jar"; |
| String javaHome = System.getProperty("java.home"); |
| File toolsJar = new File(javaHome + libToolsJar); |
| if (toolsJar.exists()) { |
| // Found in java.home as given |
| return toolsJar; |
| } |
| if (javaHome.toLowerCase(Locale.ENGLISH).endsWith(File.separator + "jre")) { |
| javaHome = javaHome.substring( |
| 0, javaHome.length() - "/jre".length()); |
| toolsJar = new File(javaHome + libToolsJar); |
| } |
| if (!toolsJar.exists()) { |
| System.out.println("Unable to locate tools.jar. " |
| + "Expected to find it in " + toolsJar.getPath()); |
| return null; |
| } |
| return toolsJar; |
| } |
| |
| /** |
| * Get an array of URLs representing all of the jar files in the |
| * given location. If the location is a file, it is returned as the only |
| * element of the array. If the location is a directory, it is scanned for |
| * jar files. |
| * |
| * @param location the location to scan for Jars. |
| * |
| * @return an array of URLs for all jars in the given location. |
| * |
| * @exception MalformedURLException if the URLs for the jars cannot be |
| * formed. |
| */ |
| public static URL[] getLocationURLs(File location) |
| throws MalformedURLException { |
| return getLocationURLs(location, new String[]{".jar"}); |
| } |
| |
| /** |
| * Get an array of URLs representing all of the files of a given set of |
| * extensions in the given location. If the location is a file, it is |
| * returned as the only element of the array. If the location is a |
| * directory, it is scanned for matching files. |
| * |
| * @param location the location to scan for files. |
| * @param extensions an array of extension that are to match in the |
| * directory search. |
| * |
| * @return an array of URLs of matching files. |
| * @exception MalformedURLException if the URLs for the files cannot be |
| * formed. |
| */ |
| public static URL[] getLocationURLs(File location, |
| final String[] extensions) |
| throws MalformedURLException { |
| URL[] urls = new URL[0]; |
| |
| if (!location.exists()) { |
| return urls; |
| } |
| if (!location.isDirectory()) { |
| urls = new URL[1]; |
| String path = location.getPath(); |
| String littlePath = path.toLowerCase(Locale.ENGLISH); |
| for (int i = 0; i < extensions.length; ++i) { |
| if (littlePath.endsWith(extensions[i])) { |
| urls[0] = fileToURL(location); |
| break; |
| } |
| } |
| return urls; |
| } |
| File[] matches = location.listFiles( |
| new FilenameFilter() { |
| public boolean accept(File dir, String name) { |
| String littleName = name.toLowerCase(Locale.ENGLISH); |
| for (int i = 0; i < extensions.length; ++i) { |
| if (littleName.endsWith(extensions[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| }); |
| urls = new URL[matches.length]; |
| for (int i = 0; i < matches.length; ++i) { |
| urls[i] = fileToURL(matches[i]); |
| } |
| return urls; |
| } |
| } |