001: /*
002: * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.tools.extcheck;
027:
028: import java.util.*;
029: import java.net.MalformedURLException;
030: import java.util.Vector;
031: import java.io.*;
032: import java.util.StringTokenizer;
033: import java.net.URL;
034: import java.util.jar.JarFile;
035: import java.util.jar.JarEntry;
036: import java.util.jar.Manifest;
037: import java.util.jar.Attributes;
038: import java.util.jar.Attributes.Name;
039: import java.net.URLConnection;
040: import java.security.Permission;
041: import java.util.jar.*;
042: import java.net.JarURLConnection;
043: import sun.net.www.ParseUtil;
044:
045: /**
046: * ExtCheck reports on clashes between a specified (target)
047: * jar file and jar files already installed in the extensions
048: * directory.
049: *
050: * @version 1.17 07/05/05
051: * @author Benedict Gomes
052: * @since 1.2
053: */
054:
055: public class ExtCheck {
056:
057: private static final boolean DEBUG = false;
058:
059: // The following strings hold the values of the version variables
060: // for the target jar file
061: private String targetSpecTitle;
062: private String targetSpecVersion;
063: private String targetSpecVendor;
064: private String targetImplTitle;
065: private String targetImplVersion;
066: private String targetImplVendor;
067: private String targetsealed;
068:
069: /* Flag to indicate whether extra information should be dumped to stdout */
070: private boolean verboseFlag;
071:
072: /*
073: * Create a new instance of the jar reporting tool for a particular
074: * targetFile.
075: * @param targetFile is the file to compare against.
076: * @param verbose indicates whether to dump filenames and manifest
077: * information (on conflict) to the standard output.
078: */
079: static ExtCheck create(File targetFile, boolean verbose) {
080: return new ExtCheck(targetFile, verbose);
081: }
082:
083: private ExtCheck(File targetFile, boolean verbose) {
084: verboseFlag = verbose;
085: investigateTarget(targetFile);
086: }
087:
088: private void investigateTarget(File targetFile) {
089: verboseMessage("Target file:" + targetFile);
090: Manifest targetManifest = null;
091: try {
092: File canon = new File(targetFile.getCanonicalPath());
093: URL url = ParseUtil.fileToEncodedURL(canon);
094: if (url != null) {
095: JarLoader loader = new JarLoader(url);
096: JarFile jarFile = loader.getJarFile();
097: targetManifest = jarFile.getManifest();
098: }
099: } catch (MalformedURLException e) {
100: error("Malformed URL ");
101: } catch (IOException e) {
102: error("IO Exception ");
103: }
104: if (targetManifest == null)
105: error("No manifest available in " + targetFile);
106: Attributes attr = targetManifest.getMainAttributes();
107: if (attr != null) {
108: targetSpecTitle = attr.getValue(Name.SPECIFICATION_TITLE);
109: targetSpecVersion = attr
110: .getValue(Name.SPECIFICATION_VERSION);
111: targetSpecVendor = attr.getValue(Name.SPECIFICATION_VENDOR);
112: targetImplTitle = attr.getValue(Name.IMPLEMENTATION_TITLE);
113: targetImplVersion = attr
114: .getValue(Name.IMPLEMENTATION_VERSION);
115: targetImplVendor = attr
116: .getValue(Name.IMPLEMENTATION_VENDOR);
117: targetsealed = attr.getValue(Name.SEALED);
118: } else {
119: error("No attributes available in the manifest");
120: }
121: if (targetSpecTitle == null)
122: error("The target file does not have a specification title");
123: if (targetSpecVersion == null)
124: error("The target file does not have a specification version");
125: verboseMessage("Specification title:" + targetSpecTitle);
126: verboseMessage("Specification version:" + targetSpecVersion);
127: if (targetSpecVendor != null)
128: verboseMessage("Specification vendor:" + targetSpecVendor);
129: if (targetImplVersion != null)
130: verboseMessage("Implementation version:"
131: + targetImplVersion);
132: if (targetImplVendor != null)
133: verboseMessage("Implementation vendor:" + targetImplVendor);
134: verboseMessage("");
135: }
136:
137: /**
138: * Verify that none of the jar files in the install directory
139: * has the same specification-title and the same or a newer
140: * specification-version.
141: *
142: * @return Return true if the target jar file is newer
143: * than any installed jar file with the same specification-title,
144: * otherwise return false
145: */
146: boolean checkInstalledAgainstTarget() {
147: String s = System.getProperty("java.ext.dirs");
148: File[] dirs;
149: if (s != null) {
150: StringTokenizer st = new StringTokenizer(s,
151: File.pathSeparator);
152: int count = st.countTokens();
153: dirs = new File[count];
154: for (int i = 0; i < count; i++) {
155: dirs[i] = new File(st.nextToken());
156: }
157: } else {
158: dirs = new File[0];
159: }
160:
161: boolean result = true;
162: for (int i = 0; i < dirs.length; i++) {
163: String[] files = dirs[i].list();
164: if (files != null) {
165: for (int j = 0; j < files.length; j++) {
166: try {
167: File f = new File(dirs[i], files[j]);
168: File canon = new File(f.getCanonicalPath());
169: URL url = ParseUtil.fileToEncodedURL(canon);
170: if (url != null) {
171: result = result
172: && checkURLRecursively(1, url);
173: }
174: } catch (MalformedURLException e) {
175: error("Malformed URL");
176: } catch (IOException e) {
177: error("IO Exception");
178: }
179: }
180: }
181: }
182: if (result) {
183: generalMessage("No conflicting installed jar found.");
184: } else {
185: generalMessage("Conflicting installed jar found. "
186: + " Use -verbose for more information.");
187: }
188: return result;
189: }
190:
191: /**
192: * Recursively verify that a jar file, and any urls mentioned
193: * in its class path, do not conflict with the target jar file.
194: *
195: * @param indent is the current nesting level
196: * @param url is the path to the jar file being checked.
197: * @return true if there is no newer URL, otherwise false
198: */
199: private boolean checkURLRecursively(int indent, URL url)
200: throws IOException {
201: verboseMessage("Comparing with " + url);
202: JarLoader jarloader = new JarLoader(url);
203: JarFile j = jarloader.getJarFile();
204: Manifest man = j.getManifest();
205: if (man != null) {
206: Attributes attr = man.getMainAttributes();
207: if (attr != null) {
208: String title = attr.getValue(Name.SPECIFICATION_TITLE);
209: String version = attr
210: .getValue(Name.SPECIFICATION_VERSION);
211: String vendor = attr
212: .getValue(Name.SPECIFICATION_VENDOR);
213: String implTitle = attr
214: .getValue(Name.IMPLEMENTATION_TITLE);
215: String implVersion = attr
216: .getValue(Name.IMPLEMENTATION_VERSION);
217: String implVendor = attr
218: .getValue(Name.IMPLEMENTATION_VENDOR);
219: String sealed = attr.getValue(Name.SEALED);
220: if (title != null) {
221: if (title.equals(targetSpecTitle)) {
222: if (version != null) {
223: if (version.equals(targetSpecVersion)
224: || isNotOlderThan(version,
225: targetSpecVersion)) {
226: verboseMessage("");
227: verboseMessage("CONFLICT DETECTED ");
228: verboseMessage("Conflicting file:"
229: + url);
230: verboseMessage("Installed Version:"
231: + version);
232: if (implTitle != null)
233: verboseMessage("Implementation Title:"
234: + implTitle);
235: if (implVersion != null)
236: verboseMessage("Implementation Version:"
237: + implVersion);
238: if (implVendor != null)
239: verboseMessage("Implementation Vendor:"
240: + implVendor);
241: return false;
242: }
243: }
244: }
245: }
246: }
247: }
248: boolean result = true;
249: URL[] loaderList = jarloader.getClassPath();
250: if (loaderList != null) {
251: for (int i = 0; i < loaderList.length; i++) {
252: if (url != null) {
253: boolean res = checkURLRecursively(indent + 1,
254: loaderList[i]);
255: result = res && result;
256: }
257: }
258: }
259: return result;
260: }
261:
262: /**
263: * See comment in method java.lang.Package.isCompatibleWith.
264: * Return true if already is not older than target. i.e. the
265: * target file may be superseded by a file already installed
266: */
267: private boolean isNotOlderThan(String already, String target)
268: throws NumberFormatException {
269: if (already == null || already.length() < 1) {
270: throw new NumberFormatException("Empty version string");
271: }
272:
273: // Until it matches scan and compare numbers
274: StringTokenizer dtok = new StringTokenizer(target, ".", true);
275: StringTokenizer stok = new StringTokenizer(already, ".", true);
276: while (dtok.hasMoreTokens() || stok.hasMoreTokens()) {
277: int dver;
278: int sver;
279: if (dtok.hasMoreTokens()) {
280: dver = Integer.parseInt(dtok.nextToken());
281: } else
282: dver = 0;
283:
284: if (stok.hasMoreTokens()) {
285: sver = Integer.parseInt(stok.nextToken());
286: } else
287: sver = 0;
288:
289: if (sver < dver)
290: return false; // Known to be incompatible
291: if (sver > dver)
292: return true; // Known to be compatible
293:
294: // Check for and absorb separators
295: if (dtok.hasMoreTokens())
296: dtok.nextToken();
297: if (stok.hasMoreTokens())
298: stok.nextToken();
299: // Compare next component
300: }
301: // All components numerically equal
302: return true;
303: }
304:
305: /**
306: * Prints out message if the verboseFlag is set
307: */
308: void verboseMessage(String message) {
309: if (verboseFlag) {
310: System.err.println(message);
311: }
312: }
313:
314: void generalMessage(String message) {
315: System.err.println(message);
316: }
317:
318: /**
319: * Print out the error message and exit from the program
320: */
321: static void error(String message) {
322: System.err.println(message);
323: System.exit(-1);
324: }
325:
326: /**
327: * Inner class used to represent a loader of resources and classes
328: * from a base URL. Somewhat modified version of code in
329: * sun.misc.URLClassPath.JarLoader
330: */
331: private static class JarLoader {
332: private final URL base;
333: private JarFile jar;
334: private URL csu;
335:
336: /*
337: * Creates a new Loader for the specified URL.
338: */
339: JarLoader(URL url) {
340: String urlName = url + "!/";
341: URL tmpBaseURL = null;
342: try {
343: tmpBaseURL = new URL("jar", "", urlName);
344: jar = findJarFile(url);
345: csu = url;
346: } catch (MalformedURLException e) {
347: ExtCheck.error("Malformed url " + urlName);
348: } catch (IOException e) {
349: ExtCheck.error("IO Exception occurred");
350: }
351: base = tmpBaseURL;
352:
353: }
354:
355: /*
356: * Returns the base URL for this Loader.
357: */
358: URL getBaseURL() {
359: return base;
360: }
361:
362: JarFile getJarFile() {
363: return jar;
364: }
365:
366: private JarFile findJarFile(URL url) throws IOException {
367: // Optimize case where url refers to a local jar file
368: if ("file".equals(url.getProtocol())) {
369: String path = url.getFile().replace('/',
370: File.separatorChar);
371: File file = new File(path);
372: if (!file.exists()) {
373: throw new FileNotFoundException(path);
374: }
375: return new JarFile(path);
376: }
377: URLConnection uc = getBaseURL().openConnection();
378: //uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
379: return ((JarURLConnection) uc).getJarFile();
380: }
381:
382: /*
383: * Returns the JAR file local class path, or null if none.
384: */
385: URL[] getClassPath() throws IOException {
386: Manifest man = jar.getManifest();
387: if (man != null) {
388: Attributes attr = man.getMainAttributes();
389: if (attr != null) {
390: String value = attr.getValue(Name.CLASS_PATH);
391: if (value != null) {
392: return parseClassPath(csu, value);
393: }
394: }
395: }
396: return null;
397: }
398:
399: /*
400: * Parses value of the Class-Path manifest attribute and returns
401: * an array of URLs relative to the specified base URL.
402: */
403: private URL[] parseClassPath(URL base, String value)
404: throws MalformedURLException {
405: StringTokenizer st = new StringTokenizer(value);
406: URL[] urls = new URL[st.countTokens()];
407: int i = 0;
408: while (st.hasMoreTokens()) {
409: String path = st.nextToken();
410: urls[i] = new URL(base, path);
411: i++;
412: }
413: return urls;
414: }
415: }
416:
417: }
|