001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.launch;
019:
020: import java.net.MalformedURLException;
021:
022: import java.net.URL;
023: import java.io.File;
024: import java.io.FilenameFilter;
025: import java.io.ByteArrayOutputStream;
026: import java.io.UnsupportedEncodingException;
027: import java.text.CharacterIterator;
028: import java.text.StringCharacterIterator;
029: import java.util.Locale;
030:
031: /**
032: * The Locator is a utility class which is used to find certain items
033: * in the environment.
034: *
035: * @since Ant 1.6
036: */
037: public final class Locator {
038: /**
039: * encoding used to represent URIs
040: */
041: public static final String URI_ENCODING = "UTF-8";
042: // stolen from org.apache.xerces.impl.XMLEntityManager#getUserDir()
043: // of the Xerces-J team
044: // which ASCII characters need to be escaped
045: private static boolean[] gNeedEscaping = new boolean[128];
046: // the first hex character if a character needs to be escaped
047: private static char[] gAfterEscaping1 = new char[128];
048: // the second hex character if a character needs to be escaped
049: private static char[] gAfterEscaping2 = new char[128];
050: private static char[] gHexChs = { '0', '1', '2', '3', '4', '5',
051: '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
052: // initialize the above 3 arrays
053: static {
054: for (int i = 0; i <= 0x1f; i++) {
055: gNeedEscaping[i] = true;
056: gAfterEscaping1[i] = gHexChs[i >> 4];
057: gAfterEscaping2[i] = gHexChs[i & 0xf];
058: }
059: gNeedEscaping[0x7f] = true;
060: gAfterEscaping1[0x7f] = '7';
061: gAfterEscaping2[0x7f] = 'F';
062: char[] escChs = { ' ', '<', '>', '#', '%', '"', '{', '}', '|',
063: '\\', '^', '~', '[', ']', '`' };
064: int len = escChs.length;
065: char ch;
066: for (int i = 0; i < len; i++) {
067: ch = escChs[i];
068: gNeedEscaping[ch] = true;
069: gAfterEscaping1[ch] = gHexChs[ch >> 4];
070: gAfterEscaping2[ch] = gHexChs[ch & 0xf];
071: }
072: }
073:
074: /**
075: * Not instantiable
076: */
077: private Locator() {
078: }
079:
080: /**
081: * Find the directory or jar file the class has been loaded from.
082: *
083: * @param c the class whose location is required.
084: * @return the file or jar with the class or null if we cannot
085: * determine the location.
086: *
087: * @since Ant 1.6
088: */
089: public static File getClassSource(Class c) {
090: String classResource = c.getName().replace('.', '/') + ".class";
091: return getResourceSource(c.getClassLoader(), classResource);
092: }
093:
094: /**
095: * Find the directory or jar a given resource has been loaded from.
096: *
097: * @param c the classloader to be consulted for the source.
098: * @param resource the resource whose location is required.
099: *
100: * @return the file with the resource source or null if
101: * we cannot determine the location.
102: *
103: * @since Ant 1.6
104: */
105: public static File getResourceSource(ClassLoader c, String resource) {
106: if (c == null) {
107: c = Locator.class.getClassLoader();
108: }
109: URL url = null;
110: if (c == null) {
111: url = ClassLoader.getSystemResource(resource);
112: } else {
113: url = c.getResource(resource);
114: }
115: if (url != null) {
116: String u = url.toString();
117: if (u.startsWith("jar:file:")) {
118: int pling = u.indexOf("!");
119: String jarName = u.substring(4, pling);
120: return new File(fromURI(jarName));
121: } else if (u.startsWith("file:")) {
122: int tail = u.indexOf(resource);
123: String dirName = u.substring(0, tail);
124: return new File(fromURI(dirName));
125: }
126: }
127: return null;
128: }
129:
130: /**
131: * Constructs a file path from a <code>file:</code> URI.
132: *
133: * <p>Will be an absolute path if the given URI is absolute.</p>
134: *
135: * <p>Prior to Java 1.4,
136: * swallows '%' that are not followed by two characters.</p>
137: *
138: * See <a href="http://www.w3.org/TR/xml11/#dt-sysid">dt-sysid</a>
139: * which makes some mention of how
140: * characters not supported by URI Reference syntax should be escaped.
141: *
142: * @param uri the URI designating a file in the local filesystem.
143: * @return the local file system path for the file.
144: * @throws IllegalArgumentException if the URI is malformed or not a legal file: URL
145: * @since Ant 1.6
146: */
147: public static String fromURI(String uri) {
148: // #8031: first try Java 1.4.
149: Class uriClazz = null;
150: try {
151: uriClazz = Class.forName("java.net.URI");
152: } catch (ClassNotFoundException cnfe) {
153: // Fine, Java 1.3 or earlier, do it by hand.
154: }
155: // Also check for properly formed URIs. Ant formerly recommended using
156: // nonsense URIs such as "file:./foo.xml" in XML includes. You shouldn't
157: // do that (just "foo.xml" is correct) but for compatibility we special-case
158: // things when the path is not absolute, and fall back to the old parsing behavior.
159: if (uriClazz != null && uri.startsWith("file:/")) {
160: try {
161: java.lang.reflect.Method createMethod = uriClazz
162: .getMethod("create",
163: new Class[] { String.class });
164: Object uriObj = createMethod.invoke(null,
165: new Object[] { uri });
166: java.lang.reflect.Constructor fileConst = File.class
167: .getConstructor(new Class[] { uriClazz });
168: File f = (File) fileConst
169: .newInstance(new Object[] { uriObj });
170: return f.getAbsolutePath();
171: } catch (java.lang.reflect.InvocationTargetException e) {
172: Throwable e2 = e.getTargetException();
173: if (e2 instanceof IllegalArgumentException) {
174: // Bad URI, pass this on.
175: throw (IllegalArgumentException) e2;
176: } else {
177: // Unexpected target exception? Should not happen.
178: e2.printStackTrace();
179: }
180: } catch (Exception e) {
181: // Reflection problems? Should not happen, debug.
182: e.printStackTrace();
183: }
184: }
185:
186: // Fallback method for Java 1.3 or earlier.
187:
188: URL url = null;
189: try {
190: url = new URL(uri);
191: } catch (MalformedURLException emYouEarlEx) {
192: // Ignore malformed exception
193: }
194: if (url == null || !("file".equals(url.getProtocol()))) {
195: throw new IllegalArgumentException(
196: "Can only handle valid file: URIs");
197: }
198: StringBuffer buf = new StringBuffer(url.getHost());
199: if (buf.length() > 0) {
200: buf.insert(0, File.separatorChar).insert(0,
201: File.separatorChar);
202: }
203: String file = url.getFile();
204: int queryPos = file.indexOf('?');
205: buf.append((queryPos < 0) ? file : file.substring(0, queryPos));
206:
207: uri = buf.toString().replace('/', File.separatorChar);
208:
209: if (File.pathSeparatorChar == ';' && uri.startsWith("\\")
210: && uri.length() > 2
211: && Character.isLetter(uri.charAt(1))
212: && uri.lastIndexOf(':') > -1) {
213: uri = uri.substring(1);
214: }
215: String path = null;
216: try {
217: path = decodeUri(uri);
218: String cwd = System.getProperty("user.dir");
219: int posi = cwd.indexOf(":");
220: if ((posi > 0) && path.startsWith(File.separator)) {
221: path = cwd.substring(0, posi + 1) + path;
222: }
223: } catch (UnsupportedEncodingException exc) {
224: // not sure whether this is clean, but this method is
225: // declared not to throw exceptions.
226: throw new IllegalStateException(
227: "Could not convert URI to path: "
228: + exc.getMessage());
229: }
230: return path;
231: }
232:
233: /**
234: * Decodes an Uri with % characters.
235: * The URI is escaped
236: * @param uri String with the uri possibly containing % characters.
237: * @return The decoded Uri
238: * @throws UnsupportedEncodingException if UTF-8 is not available
239: * @since Ant 1.7
240: */
241: public static String decodeUri(String uri)
242: throws UnsupportedEncodingException {
243: if (uri.indexOf('%') == -1) {
244: return uri;
245: }
246: ByteArrayOutputStream sb = new ByteArrayOutputStream(uri
247: .length());
248: CharacterIterator iter = new StringCharacterIterator(uri);
249: for (char c = iter.first(); c != CharacterIterator.DONE; c = iter
250: .next()) {
251: if (c == '%') {
252: char c1 = iter.next();
253: if (c1 != CharacterIterator.DONE) {
254: int i1 = Character.digit(c1, 16);
255: char c2 = iter.next();
256: if (c2 != CharacterIterator.DONE) {
257: int i2 = Character.digit(c2, 16);
258: sb.write((char) ((i1 << 4) + i2));
259: }
260: }
261: } else {
262: sb.write(c);
263: }
264: }
265: return sb.toString(URI_ENCODING);
266: }
267:
268: /**
269: * Encodes an Uri with % characters.
270: * The URI is escaped
271: * @param path String to encode.
272: * @return The encoded string, according to URI norms
273: * @throws UnsupportedEncodingException if UTF-8 is not available
274: * @since Ant 1.7
275: */
276: public static String encodeURI(String path)
277: throws UnsupportedEncodingException {
278: int i = 0;
279: int len = path.length();
280: int ch = 0;
281: StringBuffer sb = null;
282: for (; i < len; i++) {
283: ch = path.charAt(i);
284: // if it's not an ASCII character, break here, and use UTF-8 encoding
285: if (ch >= 128) {
286: break;
287: }
288: if (gNeedEscaping[ch]) {
289: if (sb == null) {
290: sb = new StringBuffer(path.substring(0, i));
291: }
292: sb.append('%');
293: sb.append(gAfterEscaping1[ch]);
294: sb.append(gAfterEscaping2[ch]);
295: // record the fact that it's escaped
296: } else if (sb != null) {
297: sb.append((char) ch);
298: }
299: }
300:
301: // we saw some non-ascii character
302: if (i < len) {
303: if (sb == null) {
304: sb = new StringBuffer(path.substring(0, i));
305: }
306: // get UTF-8 bytes for the remaining sub-string
307: byte[] bytes = null;
308: byte b;
309: bytes = path.substring(i).getBytes(URI_ENCODING);
310: len = bytes.length;
311:
312: // for each byte
313: for (i = 0; i < len; i++) {
314: b = bytes[i];
315: // for non-ascii character: make it positive, then escape
316: if (b < 0) {
317: ch = b + 256;
318: sb.append('%');
319: sb.append(gHexChs[ch >> 4]);
320: sb.append(gHexChs[ch & 0xf]);
321: } else if (gNeedEscaping[b]) {
322: sb.append('%');
323: sb.append(gAfterEscaping1[b]);
324: sb.append(gAfterEscaping2[b]);
325: } else {
326: sb.append((char) b);
327: }
328: }
329: }
330: return sb == null ? path : sb.toString();
331: }
332:
333: /**
334: * Convert a File to a URL.
335: * File.toURL() does not encode characters like #.
336: * File.toURI() has been introduced in java 1.4, so
337: * ANT cannot use it (except by reflection)
338: * FileUtils.toURI() cannot be used by Locator.java
339: * Implemented this way.
340: * File.toURL() adds file: and changes '\' to '/' for dos OSes
341: * encodeURI converts characters like ' ' and '#' to %DD
342: * @param file the file to convert
343: * @return URL the converted File
344: * @throws MalformedURLException on error
345: */
346: public static URL fileToURL(File file) throws MalformedURLException {
347: try {
348: return new URL(encodeURI(file.toURL().toString()));
349: } catch (UnsupportedEncodingException ex) {
350: throw new MalformedURLException(ex.toString());
351: }
352: }
353:
354: /**
355: * Get the File necessary to load the Sun compiler tools. If the classes
356: * are available to this class, then no additional URL is required and
357: * null is returned. This may be because the classes are explicitly in the
358: * class path or provided by the JVM directly.
359: *
360: * @return the tools jar as a File if required, null otherwise.
361: */
362: public static File getToolsJar() {
363: // firstly check if the tools jar is already in the classpath
364: boolean toolsJarAvailable = false;
365: try {
366: // just check whether this throws an exception
367: Class.forName("com.sun.tools.javac.Main");
368: toolsJarAvailable = true;
369: } catch (Exception e) {
370: try {
371: Class.forName("sun.tools.javac.Main");
372: toolsJarAvailable = true;
373: } catch (Exception e2) {
374: // ignore
375: }
376: }
377: if (toolsJarAvailable) {
378: return null;
379: }
380: // couldn't find compiler - try to find tools.jar
381: // based on java.home setting
382: String javaHome = System.getProperty("java.home");
383: File toolsJar = new File(javaHome + "/lib/tools.jar");
384: if (toolsJar.exists()) {
385: // Found in java.home as given
386: return toolsJar;
387: }
388: if (javaHome.toLowerCase(Locale.US).endsWith(
389: File.separator + "jre")) {
390: javaHome = javaHome.substring(0, javaHome.length() - 4);
391: toolsJar = new File(javaHome + "/lib/tools.jar");
392: }
393: if (!toolsJar.exists()) {
394: System.out.println("Unable to locate tools.jar. "
395: + "Expected to find it in " + toolsJar.getPath());
396: return null;
397: }
398: return toolsJar;
399: }
400:
401: /**
402: * Get an array of URLs representing all of the jar files in the
403: * given location. If the location is a file, it is returned as the only
404: * element of the array. If the location is a directory, it is scanned for
405: * jar files.
406: *
407: * @param location the location to scan for Jars.
408: *
409: * @return an array of URLs for all jars in the given location.
410: *
411: * @exception MalformedURLException if the URLs for the jars cannot be
412: * formed.
413: */
414: public static URL[] getLocationURLs(File location)
415: throws MalformedURLException {
416: return getLocationURLs(location, new String[] { ".jar" });
417: }
418:
419: /**
420: * Get an array of URLs representing all of the files of a given set of
421: * extensions in the given location. If the location is a file, it is
422: * returned as the only element of the array. If the location is a
423: * directory, it is scanned for matching files.
424: *
425: * @param location the location to scan for files.
426: * @param extensions an array of extension that are to match in the
427: * directory search.
428: *
429: * @return an array of URLs of matching files.
430: * @exception MalformedURLException if the URLs for the files cannot be
431: * formed.
432: */
433: public static URL[] getLocationURLs(File location,
434: final String[] extensions) throws MalformedURLException {
435: URL[] urls = new URL[0];
436:
437: if (!location.exists()) {
438: return urls;
439: }
440: if (!location.isDirectory()) {
441: urls = new URL[1];
442: String path = location.getPath();
443: for (int i = 0; i < extensions.length; ++i) {
444: if (path.toLowerCase().endsWith(extensions[i])) {
445: urls[0] = fileToURL(location);
446: break;
447: }
448: }
449: return urls;
450: }
451: File[] matches = location.listFiles(new FilenameFilter() {
452: public boolean accept(File dir, String name) {
453: for (int i = 0; i < extensions.length; ++i) {
454: if (name.toLowerCase().endsWith(extensions[i])) {
455: return true;
456: }
457: }
458: return false;
459: }
460: });
461: urls = new URL[matches.length];
462: for (int i = 0; i < matches.length; ++i) {
463: urls[i] = fileToURL(matches[i]);
464: }
465: return urls;
466: }
467: }
|