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

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 Strings to either
	 * Maps or {@link ClassFile}s (which are lazily created and
	 * may be null).  At each level of the nested map, the string
	 * key is a package name iff its corresponding value is a Map.
	 * Examine that Map's contents to explore the contents of
	 * that package.  If the corresponding value is a ClassFile,
	 * then the string key's value is the name of that class.  Finally, if
	 * the corresponding value is null, 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 (ClassFiles
	 * are lazily loaded to conserve memory).
	 */
	private TreeMap packageMap;

	private long lastModified;


	/**
	 * Constructor.
	 *
	 * @param info The jar file to read from.  This cannot be null.
	 * @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; i largest 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; jClassFiles 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 all ClassFiles 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 matching ClassFiles 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; icom/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





© 2015 - 2024 Weber Informatics LLC | Privacy Policy