/* * 04/21/2012 * * Copyright (C) 2010 Robert Futrell * robert_futrell at users.sourceforge.net * http://fifesoft.com/rsyntaxtextarea * * This library is distributed under a modified BSD license. See the included * RSTALanguageSupport.License.txt file for details. */ package org.fife.rsta.ac.java.buildpath; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.fife.rsta.ac.java.Util; import org.fife.rsta.ac.java.classreader.ClassFile; /** * Information about specific classes on the current application's classpath to * add to the "build path." This type of container is useful if your * application ships with specific classes you want included in code * completion, but you don't want to add the entire jar to the build path.<p> * * Since there is no real way to determine all classes in a package via * reflection, you must explicitly enumerate all classes that are on the * classpath that you want on the build path. To make this easier, you can * use the {@link ClassEnumerationReader} class to read a list of classes from * a plain text file or other resource.<p> * * If you're delivering the corresponding .java source files also on the * classpath (i.e. you have a library "hard-coded" to be on the build path), * you can set the source location to be a <code>ClasspathSourceLocation</code> * to get the source located automatically. * * @author Robert Futrell * @version 1.0 * @see JarLibraryInfo * @see DirLibraryInfo * @see ClasspathSourceLocation */ public class ClasspathLibraryInfo extends LibraryInfo { /** * Mapping of class names to <code>ClassFile</code>s. This information is * cached even though it's also cached at the <code>JarReader</code> level * because the class definitions are effectively immutable since they're * on the classpath. This allows you to theoretically share a single * <code>ClasspathLibraryInfo</code> across several different jar managers. */ private Map classNameToClassFile; /** * Constructor. * * @param classes A list of fully-qualified class names for classes you * want added to the build path. */ public ClasspathLibraryInfo(String[] classes) { this(Arrays.asList(classes), null); } /** * Constructor. * * @param classes A list of fully-qualified class names for classes you * want added to the build path. */ public ClasspathLibraryInfo(List classes) { this(classes, null); } /** * Constructor. * * @param classes A list of fully-qualified class names for classes you * want added to the build path. * @param sourceLoc The location of the source files for the classes given. * This may be <code>null</code>. */ public ClasspathLibraryInfo(List classes, SourceLocation sourceLoc) { setSourceLocation(sourceLoc); classNameToClassFile = new HashMap(); int count = classes==null ? 0 : classes.size(); for (int i=0; i<count; i++) { // Our internal map must have all entries ending in ".class", but // the one we pass to client code must not. String entryName = (String)classes.get(i); entryName = entryName.replace('.', '/') + ".class"; classNameToClassFile.put(entryName, null); } } public int compareTo(Object o) { if (o==this) { return 0; } int res = -1; if (o instanceof ClasspathLibraryInfo) { ClasspathLibraryInfo other = (ClasspathLibraryInfo)o; res = classNameToClassFile.size() - other.classNameToClassFile.size(); if (res==0) { for (Iterator i=classNameToClassFile.keySet().iterator(); i.hasNext(); ) { String key = (String)i.next(); if (!other.classNameToClassFile.containsKey(key)) { res = -1; break; } } } } return res; } public ClassFile createClassFile(String entryName) throws IOException { // NOTE: entryName always ends in ".class", so our map must account // for this. ClassFile cf = null; if (classNameToClassFile.containsKey(entryName)) { cf = (ClassFile)classNameToClassFile.get(entryName); if (cf==null) { cf = createClassFileImpl(entryName); classNameToClassFile.put(entryName, cf); } } return cf; } private ClassFile createClassFileImpl(String res) throws IOException { ClassFile cf = null; InputStream in = getClass().getClassLoader().getResourceAsStream(res); if (in!=null) { DataInputStream din = null; try { BufferedInputStream bin = new BufferedInputStream(in); din = new DataInputStream(bin); cf = new ClassFile(din); } finally { in.close(); // DIS and BIS just delegate the close to the child } } return cf; } public TreeMap createPackageMap() throws IOException { TreeMap packageMap = new TreeMap(); for (Iterator i=classNameToClassFile.keySet().iterator(); i.hasNext(); ) { String className = (String)i.next(); String[] tokens = Util.splitOnChar(className, '/'); TreeMap temp = packageMap; for (int j=0; j<tokens.length-1; j++) { String pkg = tokens[j]; TreeMap submap = (TreeMap)temp.get(pkg); if (submap==null) { submap = new TreeMap(); temp.put(pkg, submap); } temp = submap; } className = tokens[tokens.length-1]; // Our internal map must have all entries ending in ".class", but // the one we pass to client code must not. className = className.substring(0, className.length()-6); temp.put(className, null); // Lazily set value to ClassFile later } return packageMap; } /** * Since stuff on the current classpath never changes (we don't support * hotswapping), this method always returns <code>0</code>. * * @return <code>0</code> always. */ public long getLastModified() { return 0; } public String getLocationAsString() { return null; } public int hashCode() { return classNameToClassFile.hashCode(); } }