All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.weakref.jmx.internal.paranamer.JavadocParanamer Maven / Gradle / Ivy

There is a newer version: 1.26
Show newest version
/*
 * Copyright 2007 Paul Hammant
 * Copyright 2007 ThinkTank Maths Limited
 * 
 * ThinkTank Maths Limited grants a non-revocable, perpetual licence
 * to Paul Hammant for unlimited use, relicensing and redistribution. No
 * explicit permission is required from ThinkTank Maths Limited for
 * any future decisions made with regard to this file.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.weakref.jmx.internal.paranamer;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Implementation of {@link Paranamer} which can access Javadocs at runtime to extract
 * parameter names of methods. Works with:-
 * 
    *
  • Javadoc in zip file
  • *
  • Javadoc in directory
  • *
  • Javadoc at remote URL
  • *
* Future implementations may be able to take multiple sources, but this version must be * instantiated with the correct location of the Javadocs for the package you wish to * extract the parameter names. Note that if a zip archive contains multiple * "package-list" files, the first one will be used to index the packages which may be * queried. *

* Note that this does not perform any caching of entries (except what it finds in the * package-list file, which is very lightweight)... every lookup will involve a disc hit. * If you want to speed up performance, use a {@link CachingParanamer}. *

* Implementation note: the constructors of this implementation let the client know if I/O * problems will stop the recovery of parameter names. It might be preferable to suppress * exceptions and simply return NO_PARAMETER_NAMES_LIST. *

* TODO: example use code *

* Known issues:- *

    *
  • Only tested with Javadoc 1.3 - 1.6
  • *
  • Doesn't handle methods that declare the generic type as a parameter (rare use case)
  • *
  • Some "erased" generic methods fail, e.g. File.compareTo(File), which is erased to * File.compareTo(Object).
  • *
  • URL implementation is really slow
  • *
  • Doesn't support nested classes (due to limitations in the Java 1.4 reflection API)
  • *
* * @author Samuel Halliday, ThinkTank Maths Limited */ public class JavadocParanamer implements Paranamer { private static final String IE = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)"; private static final ParameterNamesNotFoundException CLASS_NOT_SUPPORTED = new ParameterNamesNotFoundException("class not supported"); /** In the case of an archive, this stores the path up to the base of the Javadocs */ private String base = null; private final boolean isArchive; private final boolean isDirectory; private final boolean isURI; /** * Regardless of the implementation, this stores the base location of the remote or * local file or directory. */ private final URI location; /** The packages which are supported by this instance. Contains Strings */ private final Set packages = new HashSet(); /** * Construct a Javadoc reading implementation of {@link Paranamer} using a local * directory or zip archive as a source. * * @param archiveOrDirectory * either a zip archive of Javadocs or the base directory of Javadocs. * @throws IOException * if there was an error when reading from either the archive or the * package-list file. * @throws FileNotFoundException * if the archive, directory or package-list file does not * exist. * @throws NullPointerException * if any parameter is null * @throws IllegalArgumentException * If the given parameter is not a file or directory or if it is a file * but not a javadoc zip archive. */ public JavadocParanamer(File archiveOrDirectory) throws IOException { if (archiveOrDirectory == null) throw new NullPointerException(); if (!archiveOrDirectory.exists()) throw new FileNotFoundException( archiveOrDirectory.getAbsolutePath()); isURI = false; location = archiveOrDirectory.toURI(); if (archiveOrDirectory.isDirectory()) { // is a directory isArchive = false; isDirectory = true; // check that "package-list" exists File dir = archiveOrDirectory; File packageList = new File(dir.getAbsolutePath() + "/package-list"); if (!packageList.isFile()) throw new FileNotFoundException("No package-list found at " + dir.getAbsolutePath() + ". Not a valid Javadoc directory."); // it appear to be a valid Javadoc directory FileInputStream input = new FileInputStream(packageList); try { String packageListString = streamToString(input); parsePackageList(packageListString); } finally { input.close(); } } else if (archiveOrDirectory.isFile()) { // is a file isArchive = true; isDirectory = false; File archive = archiveOrDirectory; if (!archive.getAbsolutePath().toLowerCase().endsWith(".zip")) throw new IllegalArgumentException(archive.getAbsolutePath() + " is not a zip file."); // check that a "package-list" exists somewhere in the archive ZipFile zip = new ZipFile(archive); try { // we need to check for a file named "package-list". // There may be multiple files in the archive // but we cannot use ZipFile.getEntry for suffix names // so we have to look through all the entries. // We then pick the largest file. Enumeration entries = zip.entries(); // grr... http://javablog.co.uk/2007/11/25/enumeration-and-iterable // Set SortedMap packageLists = new TreeMap(); while (entries.hasMoreElements()) { ZipEntry entry = (ZipEntry) entries.nextElement(); String name = entry.getName(); if (name.endsWith("package-list")) { Long size = new Long(entry.getSize()); packageLists.put(size, entry); } } if (packageLists.size() == 0) throw new FileNotFoundException( "no package-list found in archive"); // pick the largest package-list file, it's most likely the one we want ZipEntry entry = (ZipEntry) packageLists.get(packageLists.lastKey()); String name = entry.getName(); base = name.substring(0, name.length() - "package-list".length()); InputStream input = zip.getInputStream(entry); try { String packageListString = streamToString(input); parsePackageList(packageListString); } finally { input.close(); } } finally { zip.close(); } } else throw new IllegalArgumentException( archiveOrDirectory.getAbsolutePath() + " is neither a directory nor a file."); } /** * @param url * @throws IOException * if there was a problem connecting to the remote Javadocs * @throws FileNotFoundException * if the url does not have a /package-list * @throws NullPointerException * if any parameter is null */ public JavadocParanamer(URL url) throws IOException { if (url == null) throw new NullPointerException(); isArchive = false; isDirectory = false; isURI = true; try { location = new URI(url.toString()); } catch (URISyntaxException e) { throw new IOException(e.getMessage()); } // check the package-list URL packageListURL = new URL(url.toString() + "/package-list"); InputStream input = urlToInputStream(packageListURL); try { String packageList = streamToString(input); parsePackageList(packageList); } finally { input.close(); } } public String[] lookupParameterNames(AccessibleObject methodOrConstructor) { return lookupParameterNames(methodOrConstructor, true); } public String[] lookupParameterNames(AccessibleObject methodOrConstructor, boolean throwExceptionIfMissing) { if (methodOrConstructor == null) throw new NullPointerException(); Class klass; String name; Class[] types; if (methodOrConstructor instanceof Constructor) { Constructor constructor = (Constructor) methodOrConstructor; klass = constructor.getDeclaringClass(); name = constructor.getName(); types = constructor.getParameterTypes(); } else if (methodOrConstructor instanceof Method) { Method method = (Method) methodOrConstructor; klass = method.getDeclaringClass(); name = method.getName(); types = method.getParameterTypes(); } else throw new IllegalArgumentException(); // quick check to see if we support the package if (!packages.contains(klass.getPackage().getName())) throw CLASS_NOT_SUPPORTED; try { String[] names = getParameterNames(klass, name, types); if (names == null) { if (throwExceptionIfMissing) { throw new ParameterNamesNotFoundException( methodOrConstructor.toString()); } else { return Paranamer.EMPTY_NAMES; } } return names; } catch (IOException e) { if (throwExceptionIfMissing) { throw new ParameterNamesNotFoundException( methodOrConstructor.toString() + " due to an I/O error: " + e.getMessage()); } else { return Paranamer.EMPTY_NAMES; } } } // throws CLASS_NOT_SUPPORTED if the class file is not found in the javadocs // return null if the parameter names were not found private String[] getParameterNames(Class klass, String constructorOrMethodName, Class[] types) throws IOException { // silly request for names of a parameterless method/constructor! if ((types != null) && (types.length == 0)) return new String[0]; String path = getCanonicalName(klass).replace('.', '/'); if (isArchive) { ZipFile archive = new ZipFile(new File(location)); ZipEntry entry = archive.getEntry(base + path + ".html"); if (entry == null) throw CLASS_NOT_SUPPORTED; InputStream input = archive.getInputStream(entry); return getParameterNames2(input, constructorOrMethodName, types); } else if (isDirectory) { File file = new File(location.getPath() + "/" + path + ".html"); if (!file.isFile()) throw CLASS_NOT_SUPPORTED; FileInputStream input = new FileInputStream(file); return getParameterNames2(input, constructorOrMethodName, types); } else if (isURI) { try { URL url = new URL(location.toString() + "/" + path + ".html"); InputStream input = urlToInputStream(url); return getParameterNames2(input, constructorOrMethodName, types); } catch (FileNotFoundException e) { throw CLASS_NOT_SUPPORTED; } } throw new RuntimeException( "bug in JavadocParanamer. Should not reach here."); } /* * Parse the Javadoc String and return the parameter names for the given constructor * or method. Return null if no method/constructor is found. Note that types will * never have length zero... we already deal with that situation higher up in the * chain. Don't forget to close the input! */ private String[] getParameterNames2(InputStream input, String constructorOrMethodName, Class[] types) throws IOException { String javadoc = streamToString(input); input.close(); // String we're looking for is like // // NAME="constructorOrMethodName(obj.ClassName, ...)"...noise... //
Parameters:
parameter_name_1...noise... //
parameter_name_2...noise... // ... //
parameter_name_N...noise... // // We cannot rely on the Parameters line existing as it depends on the author // having correctly marked-up their code. The NAME element is auto-generated // and should be checked for aggressively. // // Also note that Javadoc parameter names may differ from the names in the source. // we don't have Pattern/Matcher :-( StringBuffer regex = new StringBuffer(); regex.append("NAME=\""); regex.append(constructorOrMethodName); // quotes needed to escape array brackets regex.append("\\(\\Q"); for (int i = 0; i < types.length; i++) { if (i != 0) regex.append(", "); // canonical name deals with arrays regex.append(getCanonicalName(types[i])); } regex.append("\\E\\)\""); // FIXME: handle Javadoc 1.3, 1.4 and 1.5 as well (this is 1.6) Pattern pattern = Pattern.compile(regex.toString()); Matcher matcher = pattern.matcher(javadoc); if (!matcher.find()) // not found return Paranamer.EMPTY_NAMES; // found it. Lookup the parameter names. String[] names = new String[types.length]; // now we're sure we have the right method, find the parameter names! String regexParams = "
([^<]*)"; Pattern patternParams = Pattern.compile(regexParams); int start = matcher.end(); Matcher matcherParams = patternParams.matcher(javadoc); for (int i = 0; i < types.length; i++) { boolean find = matcherParams.find(start); if (!find) return Paranamer.EMPTY_NAMES; start = matcherParams.end(); names[i] = matcherParams.group(1); } return names; } // doesn't support names of nested classes private String getCanonicalName(Class klass) { if (klass.isArray()) return getCanonicalName(klass.getComponentType()) + "[]"; return klass.getName(); } // storing the list of packages that we support is very lightweight private void parsePackageList(String packageList) throws IOException { StringReader reader = new StringReader(packageList); BufferedReader breader = new BufferedReader(reader); String line; while ((line = breader.readLine()) != null) { packages.add(line); } } // read an InputStream into a UTF-8 String private String streamToString(InputStream input) throws IOException { InputStreamReader reader; try { reader = new InputStreamReader(input, "UTF-8"); } catch (UnsupportedEncodingException e) { // this should never happen reader = new InputStreamReader(input); } BufferedReader breader = new BufferedReader(reader); String line; StringBuffer builder = new StringBuffer(); while ((line = breader.readLine()) != null) { builder.append(line); builder.append("\n"); } return builder.toString(); } private InputStream urlToInputStream(URL url) throws IOException { URLConnection conn = url.openConnection(); // pretend to be IE6 conn.setRequestProperty("User-Agent", IE); // allow both GZip and Deflate (ZLib) encodings conn.setRequestProperty("Accept-Encoding", "gzip, deflate"); conn.connect(); String encoding = conn.getContentEncoding(); if ((encoding != null) && encoding.equalsIgnoreCase("gzip")) return new GZIPInputStream(conn.getInputStream()); else if ((encoding != null) && encoding.equalsIgnoreCase("deflate")) return new InflaterInputStream(conn.getInputStream(), new Inflater( true)); else return conn.getInputStream(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy