org.fife.rsta.ac.java.JarReader Maven / Gradle / Ivy
/* * 03/21/2010 * * 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; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import org.fife.rsta.ac.java.classreader.ClassFile; import org.fife.ui.autocomplete.CompletionProvider; /** * Reads entries from a Jar. * * @author Robert Futrell * @version 1.0 */ class JarReader { /** * Information about the jar or directory we're reading classes from. */ private JarInfo info; /** * This is essentially a tree model of all classes in the jar or * directory. It's a recursive mapping of
s are cleared, in case any classes have been * updated. */ private void checkLastModified() { long newLastModified = info.getJarFile().lastModified(); if (newLastModified!=0 && newLastModified!=lastModified) { int count = 0; count = clearClassFiles(packageMap); System.out.println("DEBUG: Cleared " + count + " cached ClassFiles"); lastModified = newLastModified; } } /** * Removes allString
s to either *Map
s or {@link ClassFile}s (which are lazily created and * may benull
). At each level of the nested map, the string * key is a package name iff its corresponding value is aMap
. * Examine thatMap
's contents to explore the contents of * that package. If the corresponding value is aClassFile
, * then the string key's value is the name of that class. Finally, if * the corresponding value isnull
, then the string key's * value is the name of a class, but its contents have not yet been * loaded for use by the code completion library (ClassFile
s * are lazily loaded to conserve memory). */ private TreeMap packageMap; private long lastModified; /** * Constructor. * * @param info The jar file to read from. This cannot benull
. * @throws IOException If an IO error occurs reading from the jar file. */ public JarReader(JarInfo info) throws IOException { this.info = info; packageMap = new TreeMap(String.CASE_INSENSITIVE_ORDER); loadCompletions(); } /** * Gets the completions in this jar that match a given string. * * @param provider The parent completion provider. * @param pkgNames The text to match, split into tokens around the * '.
' character. This should be (the start of) a * fully-qualified class, interface, or enum name. * @param addTo The list to add completion choices to. */ public void addCompletions(CompletionProvider provider, String[] pkgNames, Set addTo) { checkLastModified(); TreeMap map = packageMap; for (int i=0; ilargest valid class char SortedMap sm = map.subMap(fromKey, toKey); for (Iterator i=sm.keySet().iterator(); i.hasNext(); ) { Object obj = i.next(); //System.out.println(obj + " - " + sm.get(obj)); Object value = sm.get(obj); // See if this is a class, and we already have the ClassFile if (value instanceof ClassFile) { ClassFile cf = (ClassFile)value; boolean inPkg = false; // TODO: Pass me in if (inPkg || org.fife.rsta.ac.java.classreader.Util.isPublic(cf.getAccessFlags())) { addTo.add(new ClassCompletion(provider, cf)); } } // If a ClassFile isn't cached, it's either a class that hasn't // had its ClassFile cached yet, or a package. else { String[] items = new String[pkgNames.length]; System.arraycopy(pkgNames, 0, items, 0, pkgNames.length-1); items[items.length-1] = obj.toString(); ClassFile cf = getClassEntry(items); if (cf!=null) { boolean inPkg = false; // TODO: Pass me in if (inPkg || org.fife.rsta.ac.java.classreader.Util.isPublic(cf.getAccessFlags())) { addTo.add(new ClassCompletion(provider, cf)); } } else { StringBuffer sb = new StringBuffer(); for (int j=0; j ClassFile ClassFile
s from a map. * * @param map The map. * @return The number of class file entries removed. */ private int clearClassFiles(Map map) { int clearedCount = 0; for (Iterator i=map.entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Map.Entry)i.next(); Object value = entry.getValue(); if (value instanceof ClassFile) { entry.setValue(null); clearedCount++; } else if (value instanceof Map) { clearedCount += clearClassFiles((Map)value); } } return clearedCount; } public boolean containsClass(String className) { String[] items = className.split("\\."); TreeMap m = packageMap; for (int i=0; inull, but may of course be * empty. */ public List getClassesWithNamesStartingWith(String prefix) { List res = new ArrayList(); String currentPkg = ""; // Don't use null; we're appending to it getClassesWithNamesStartingWithImpl(prefix, packageMap, currentPkg, res); return res; } /** * Method used to recursively scan our package map for classes whose names * start with a given prefix, ignoring case. * * @param prefix The prefix that the unqualified class names must match * (ignoring case). * @param map A piece of our package map. * @param currentPkg The package that map
belongs to (i.e. * all levels of packages scanned before this one), separated by * '/
'. * @param addTo The list to add any matchingClassFile
s to. */ private void getClassesWithNamesStartingWithImpl(String prefix, Map map, String currentPkg, List addTo) { final int prefixLen = prefix.length(); // Loop through the map's entries, which are String keys mapping to // one of a Map (if the key is a package name), a ClassFile (if the // key is a class name), or null (if the key is a class name, but the // corresponding ClassFile has not been loaded yet). for (Iterator i=map.entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Map.Entry)i.next(); String key = (String)entry.getKey(); Object value = entry.getValue(); if (value instanceof Map) { getClassesWithNamesStartingWithImpl(prefix, (Map)value, currentPkg + key + "/", addTo); } else { // value is either a ClassFile or null // If value is null, we only lazily create the ClassFile if // necessary (i.e. if the class name does match what they've // typed). String className = key; if (className.regionMatches(true, 0, prefix, 0, prefixLen)) { if (value==null) { String fqClassName = currentPkg + className + ".class"; try { value = createClassFile(fqClassName); entry.setValue(value); // Update the map } catch (IOException ioe) { ioe.printStackTrace(); } } if (value!=null) { // possibly null if IOException above addTo.add(/*(ClassFile)*/value); } } } } } /** * Returns the physical file on disk.* * Modifying the returned object will not have any effect on * code completion; e.g. changing the source location will not have any * effect. * * @return The info. */ public JarInfo getJarInfo() { return (JarInfo)info.clone(); } public SortedMap getPackageEntry(String[] pkgs) { SortedMap map = packageMap; for (int i=0; i
com/company/pkgname"... * @throws IOException If an IO error occurs. */ private void loadCompletionsDirectoryImpl(File dir, String pkg) throws IOException { File[] children = dir.listFiles(); TreeMap m = packageMap; boolean firstTimeThrough = true; for (int i=0; i