* 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.HashMap;
import java.util.Iterator;
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.
* @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;
* @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);
* @param classes A list of fully-qualified class names for classes you
* want added to the build path.
public ClasspathLibraryInfo(List classes) {
* @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 instanceof ClasspathLibraryInfo) {
ClasspathLibraryInfo other = (ClasspathLibraryInfo)o;
res = classNameToClassFile.size() -
other.classNameToClassFile.size();
for (Iterator i=classNameToClassFile.keySet().iterator();
String key = (String)i.next();
if (!other.classNameToClassFile.containsKey(key)) {
public ClassFile createClassFile(String entryName) throws IOException {
// NOTE: entryName always ends in ".class", so our map must account
if (classNameToClassFile.containsKey(entryName)) {
cf = (ClassFile)classNameToClassFile.get(entryName);
cf = createClassFileImpl(entryName);
classNameToClassFile.put(entryName, cf);
private ClassFile createClassFileImpl(String res) throws IOException {
InputStream in = getClass().getClassLoader().getResourceAsStream(res);
DataInputStream din = null;
BufferedInputStream bin = new BufferedInputStream(in);
din = new DataInputStream(bin);
in.close(); // DIS and BIS just delegate the close to the child
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++) {
TreeMap submap = (TreeMap)temp.get(pkg);
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
* 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() {
public String getLocationAsString() {
return classNameToClassFile.hashCode();