package tufts.vue;

import java.awt.Checkbox;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.StreamTokenizer;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JTextArea;

import edu.tufts.vue.metadata.MetadataList;

import tufts.vue.LWComponent;
import tufts.vue.LWLink;
import tufts.vue.LWMap;
import tufts.vue.LWNode;
import tufts.vue.LayoutAction;
import tufts.vue.VUE;
import tufts.vue.VueResources;
import tufts.vue.VueUtil;
import tufts.vue.gui.DockWindow;
import tufts.vue.gui.GUI;
import tufts.vue.gui.VueFileChooser;

public class JavaAnalysisPanel extends JPanel implements ActionListener {
	private static final long			serialVersionUID = 1L;
	protected static final org.apache.log4j.Logger
										Log = org.apache.log4j.Logger.getLogger(JavaAnalysisPanel.class);
	protected static final String		PUBLIC_KEYWORD = "public",
										ABSTRACT_KEYWORD = "abstract",
										FINAL_KEYWORD = "final",
										CLASS_KEYWORD = "class",
										INTERFACE_KEYWORD = "interface",
										EXTENDS_KEYWORD = "extends",
										IMPLEMENTS_KEYWORD = "implements",
										METADATA_CATEGORY = "Java Analysis",
										METADATA_KEYWORD_UNDECLARED = "undeclared",
										METADATA_KEYWORD_DECLARED = "declared in ",
										JAVA_ANALYSIS = VueResources.getString("analyze.java.javaAnalysis"),
										HELP_TEXT = VueResources.getString("dockWindow.JavaAnalysis.helpText"),
										DEFAULT_FILE_CHOOSER_TITLE = VueResources.getString("FileChooser.openDialogTitleText"),
										DEFAULT_FILE_CHOOSER_OPEN_BUTTON = VueResources.getString("FileChooser.openButtonText"),
										DEFAULT_FILE_CHOOSER_OPEN_BUTTON_TOOLTIP = VueResources.getString("FileChooser.openButtonToolTipText"),
										CHOOSE_FILES = VueResources.getString("analyze.java.chooseFiles"),
										ANALYZE_CLASSES = VueResources.getString("analyze.java.analyzeClasses"),
										ANALYZE_INNER_CLASSES = VueResources.getString("analyze.java.analyzeInnerClasses"),
										ANALYZE_INTERFACES = VueResources.getString("analyze.java.analyzeInterfaces"),
										ANALYZE_INNER_INTERFACES = VueResources.getString("analyze.java.analyzeInnerInterfaces"),
										ANALYZE = VueResources.getString("analyze.java.analyze"),
										ANALYZE_TOOLTIP = VueResources.getString("analyze.java.analyzeTooltip"),
										STOP = VueResources.getString("analyze.java.stop"),
										PARSING = VueResources.getString("analyze.java.parsing"),
										DISPLAYING = VueResources.getString("analyze.java.displaying"),
										NO_JAVA_CLASS = VueResources.getString("analyze.java.noJavaClass"),
										JAVA_ANALYSIS_ERROR = VueResources.getString("analyze.java.error");
	protected static final Color		DECLARED_COLOR = Color.BLACK,
										UNDECLARED_COLOR = Color.LIGHT_GRAY;
	protected static final LWComponent.StrokeStyle
										ABSTRACT_STROKE_STYLE = LWComponent.StrokeStyle.DASHED,
										NONABSTRACT_STROKE_STYLE = LWComponent.StrokeStyle.SOLID;
	protected static final float		FINAL_STROKE_WIDTH = 2f,
										NONFINAL_STROKE_WIDTH = 1f;
	protected static final Class<? extends RectangularShape>
										CLASS_SHAPE = Ellipse2D.Float.class,
										INTERFACE_SHAPE = Rectangle2D.Float.class;
	protected static final char			DOLLAR_TOKEN = '$',
										UNDERSCORE_TOKEN = '_',
										COMMA_TOKEN = ',',
										SEMICOLON_TOKEN = ';',
										LEFT_ANGLE_TOKEN = '<',
										RIGHT_ANGLE_TOKEN = '>';
	protected static final int			CLASSES_MASK = 1,
										INNER_CLASSES_MASK = 2,
										INTERFACES_MASK = 4,
										INNER_INTERFACES_MASK = 8,
										HALF_GUTTER = 4;
	protected static final Insets		HALF_GUTTER_INSETS = new Insets(HALF_GUTTER, HALF_GUTTER, HALF_GUTTER, HALF_GUTTER);
	protected static DockWindow			dock = null;
	protected static final boolean		DEBUG = false;

	protected JPanel					contentPanel = null;
	protected JCheckBox					classesCheckBox = null,
										innerClassesCheckBox = null,
										interfacesCheckBox = null,
										innerInterfacesCheckBox = null;
	protected JTextArea					messageTextArea = null;
	protected JButton					analyzeButton = null;
	protected File						lastDirectory = null;
	protected List<LWNode>				allNodes= null;
	protected List<LWComponent>			newComps= null;
	protected Hashtable<String, LWNode>	classHash = null;
	protected int						totalFoundCount = 0;
	protected boolean					proceedWithAnalysis = true;
	AnalysisThread						analysisThread = null;
	FileFilter							currentFileFilter = null;


	public JavaAnalysisPanel() {
		super(new GridBagLayout());

		contentPanel = new JPanel(new GridBagLayout());
		classesCheckBox = new JCheckBox(ANALYZE_CLASSES);
		innerClassesCheckBox = new JCheckBox(ANALYZE_INNER_CLASSES);
		interfacesCheckBox = new JCheckBox(ANALYZE_INTERFACES);
		innerInterfacesCheckBox = new JCheckBox(ANALYZE_INNER_INTERFACES);
		messageTextArea = new JTextArea();
		analyzeButton = new JButton(CHOOSE_FILES);

		classesCheckBox.setSelected(true);
		innerClassesCheckBox.setSelected(false);
		interfacesCheckBox.setSelected(true);
		innerInterfacesCheckBox.setSelected(false);

		classesCheckBox.addActionListener(this);
		innerClassesCheckBox.addActionListener(this);
		interfacesCheckBox.addActionListener(this);
		innerInterfacesCheckBox.addActionListener(this);
		analyzeButton.addActionListener(this);

		classesCheckBox.setFont(tufts.vue.gui.GUI.LabelFace);
		innerClassesCheckBox.setFont(tufts.vue.gui.GUI.LabelFace);
		interfacesCheckBox.setFont(tufts.vue.gui.GUI.LabelFace);
		innerInterfacesCheckBox.setFont(tufts.vue.gui.GUI.LabelFace);
		messageTextArea.setFont(tufts.vue.gui.GUI.LabelFace);
		analyzeButton.setFont(tufts.vue.gui.GUI.LabelFace);

		messageTextArea.setBackground(contentPanel.getBackground());

		int			checkbox_width = (int)(innerClassesCheckBox.getPreferredSize().getHeight());
		Insets		HALF_GUTTER_INSETS_INDENTED = new Insets(0, checkbox_width, HALF_GUTTER, HALF_GUTTER);

		addToGridBag(contentPanel, classesCheckBox,         0, 0, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, 0.0, 0.0, HALF_GUTTER_INSETS);
		addToGridBag(contentPanel, innerClassesCheckBox,    0, 1, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, 0.0, 0.0, HALF_GUTTER_INSETS_INDENTED);
		addToGridBag(contentPanel, interfacesCheckBox,      0, 2, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, 0.0, 0.0, HALF_GUTTER_INSETS);
		addToGridBag(contentPanel, innerInterfacesCheckBox, 0, 3, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, 0.0, 0.0, HALF_GUTTER_INSETS_INDENTED);
		addToGridBag(contentPanel, messageTextArea,         0, 4, 1, 1, GridBagConstraints.CENTER,    GridBagConstraints.BOTH, 1.0, 1.0, HALF_GUTTER_INSETS);
		addToGridBag(contentPanel, analyzeButton,           0, 5, 1, 1, GridBagConstraints.SOUTHEAST, GridBagConstraints.NONE, 0.0, 0.0, HALF_GUTTER_INSETS);
		addToGridBag(this,         contentPanel,            0, 0, 1, 1, GridBagConstraints.CENTER,    GridBagConstraints.BOTH, 1.0, 1.0, HALF_GUTTER_INSETS);

		if (DEBUG) {
			contentPanel.setBackground(Color.CYAN);
			classesCheckBox.setBackground(Color.MAGENTA);
			classesCheckBox.setOpaque(true);
			innerClassesCheckBox.setBackground(Color.MAGENTA);
			innerClassesCheckBox.setOpaque(true);
			interfacesCheckBox.setBackground(Color.MAGENTA);
			interfacesCheckBox.setOpaque(true);
			innerInterfacesCheckBox.setBackground(Color.MAGENTA);
			innerInterfacesCheckBox.setOpaque(true);
			messageTextArea.setBackground(Color.MAGENTA);
			analyzeButton.setBackground(Color.MAGENTA);
			analyzeButton.setOpaque(true);
		}
	}


	public void finalize() {
		contentPanel = null;
		classesCheckBox = null;
		innerClassesCheckBox = null;
		interfacesCheckBox = null;
		innerInterfacesCheckBox = null;
		messageTextArea = null;
		analyzeButton = null;
		lastDirectory = null;
		allNodes = null;
		newComps = null;
		classHash = null;
		analysisThread = null;
		currentFileFilter = null;
	}


	public static DockWindow getJavaAnalysisDock() {
		if (dock == null) {
			JavaAnalysisPanel	analysisPanel = new JavaAnalysisPanel();

			dock = GUI.createDockWindow(JAVA_ANALYSIS, HELP_TEXT, analysisPanel);
			dock.pack();
			dock.setResizeEnabled(true);
		}

		return dock;
	}


	protected void analyze(int mask) {
		// Parse each file, then display the results.
		File		files[] = showFileChooser();

		if (files != null) {
			analyzeButton.setText(STOP);

			int			fileCount = files.length,
						fileIndex;
			Point2D		viewerCenter = VUE.getActiveViewer().getVisibleMapCenter();
			double		viewerCenterX = viewerCenter.getX(),
						viewerCenterY = viewerCenter.getY();

			if (DEBUG) {
				Log.info("analyzing " + fileCount + " file" + (fileCount == 1 ? "." : "s."));
			}

			newComps = new ArrayList<LWComponent>();
			findNodesOnMap();
			totalFoundCount = 0;

			messageTextArea.setText(PARSING);

			for (fileIndex = 0; fileIndex < fileCount && proceedWithAnalysis; fileIndex++) {
				File	file = files[fileIndex];
				int		foundCount;

				if (file.isDirectory()) {
					File	filesInFolder[] = file.listFiles(currentFileFilter);
					int		fileInFolderCount = filesInFolder.length,
							fileInFolderIndex;

					for (fileInFolderIndex = 0; fileInFolderIndex < fileInFolderCount && proceedWithAnalysis; fileInFolderIndex++) {
						File	fileInFolder = filesInFolder[fileInFolderIndex];

						if (!fileInFolder.isDirectory()) {
							foundCount = parseFile(fileInFolder, mask, viewerCenterX, viewerCenterY);

							if (foundCount > 0) {
								totalFoundCount += foundCount;
							} else {
								VueUtil.alert(String.format(NO_JAVA_CLASS, fileInFolder.getName()), JAVA_ANALYSIS);
							}
						}
					}
				}
				else {
					foundCount = parseFile(file, mask, viewerCenterX, viewerCenterY);

					if (foundCount > 0) {
						totalFoundCount += foundCount;
					} else {
						VueUtil.alert(String.format(NO_JAVA_CLASS, file.getName()), JAVA_ANALYSIS);
					}
				}
			}

			if (proceedWithAnalysis) {
				messageTextArea.setText(DISPLAYING);
				analyzeButton.setEnabled(false);

				GUI.invokeAfterAWT(new Runnable() { public void run() {
					displayResults();

					allNodes = null;
					newComps = null;
					classHash = null;
					totalFoundCount = 0;

					messageTextArea.setText("");
					analyzeButton.setText(CHOOSE_FILES);
					analyzeButton.setEnabled(true);
				}});
			}
		}
	}


	protected void findNodesOnMap() {
		Iterable<LWComponent>	comps = VUE.getActiveMap().getAllDescendents();

		allNodes = new ArrayList<LWNode>();
		classHash = new Hashtable<String, LWNode>();

		for (LWComponent comp : comps) {
			if (comp instanceof LWNode) {
				String	label = comp.getLabel();

				if (DEBUG) {
					Log.info("nodesOnMap() found " + label + ".");
				}

				allNodes.add((LWNode)comp);
				classHash.put(label, (LWNode)comp);
			}
		}
	}


	protected int parseFile(File file, int mask, double x, double y) {
		int		foundCount = 0;
		String	filename = file.getName();

		try {
			if (filename.endsWith(JavaSourceFileFilter.FILE_EXTENSION)) {
				foundCount = parseSourceFile(file, filename, mask, x, y);
			} else if (filename.endsWith(JavaClassFileFilter.FILE_EXTENSION)) {
				FileInputStream		stream = new FileInputStream(file);
				DataInputStream		dataStream = new DataInputStream(stream);

				foundCount = parseClassFile(dataStream, filename, mask, x, y);
			} else if (filename.endsWith(JavaArchiveFileFilter.FILE_EXTENSION)) {
				JarFile					jar = new JarFile(file);
				Enumeration<JarEntry>	jarEntries = jar.entries();

				while (jarEntries.hasMoreElements()) {
					try {
						JarEntry	jarEntry = jarEntries.nextElement();
						String		name = jarEntry.getName();

						if (name.endsWith(JavaClassFileFilter.FILE_EXTENSION)) {
							InputStream			stream = jar.getInputStream(jarEntry);
							DataInputStream		dataStream = new DataInputStream(stream);

							foundCount += parseClassFile(dataStream, name, mask, x, y);
						}
					} catch(Exception ex) {
						ex.printStackTrace();
						VueUtil.alert(ex.getMessage(), JAVA_ANALYSIS_ERROR);
					}
				}
			}
		} catch(Exception ex) {
			ex.printStackTrace();
			VueUtil.alert(ex.getMessage(), JAVA_ANALYSIS_ERROR);
		}

		return foundCount;
	}


	protected int parseSourceFile(File file, String filename, int mask, double x, double y) throws java.io.IOException {
		// Open and read the file, looking for declarations of Java classes or interfaces.
		int					foundCount = 0;
		boolean				analyzeClasses = (mask & CLASSES_MASK) == CLASSES_MASK,
							analyzeInnerClasses = (mask & INNER_CLASSES_MASK) == INNER_CLASSES_MASK,
							analyzeInterfaces = (mask & INTERFACES_MASK) == INTERFACES_MASK,
							analyzeInnerInterfaces = (mask & INNER_INTERFACES_MASK) == INNER_INTERFACES_MASK,
							isPublic = false,
							isAbstract = false,
							isFinal = false;

		FileReader		stream = new FileReader(file);
		BufferedReader	reader = new BufferedReader(stream);
		StreamTokenizer	tokenizer = new StreamTokenizer(reader);
		String			sourceName = filename.replace(JavaSourceFileFilter.FILE_EXTENSION, "");

		if (DEBUG) {
			Log.info("parseSourceFile() analyzing " + filename + " for" +
				(analyzeClasses ? " classes" + (analyzeInnerClasses ? " (including inner classes)" : "") : "") +
				(analyzeInterfaces ? (analyzeClasses ? "," : "") + " interfaces" + ((analyzeInnerInterfaces ? " (including inner interfaces)" : "")) : "") + ".");
		}

		tokenizer.wordChars(DOLLAR_TOKEN, DOLLAR_TOKEN);
		tokenizer.wordChars(UNDERSCORE_TOKEN, UNDERSCORE_TOKEN);
		tokenizer.wordChars('0', '9');
		tokenizer.slashSlashComments(true);
		tokenizer.slashStarComments(true);

		while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {
			if (tokenizer.ttype == SEMICOLON_TOKEN) {
				// This is the end of a declaration (or statement), so the public, abstract and final
				// keywords -- if they had been found -- are not applicable for the next declaration.
				isPublic = false;
				isAbstract = false;
				isFinal = false;
			} else if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
				if (tokenizer.sval.equals(PUBLIC_KEYWORD)) {
					isPublic = true;
				} else if (tokenizer.sval.equals(ABSTRACT_KEYWORD)) {
					isAbstract = true;
				} else if (tokenizer.sval.equals(FINAL_KEYWORD)) {
					isFinal = true;
				} else if (tokenizer.sval.equals(CLASS_KEYWORD)) {
					if (analyzeClasses) {
						if (tokenizer.nextToken() == StreamTokenizer.TT_WORD) {
							String				className = tokenizer.sval,
												extendsClassName = null;
							ArrayList<String>	implementsInterfaceNames = null;

							skipGeneric(tokenizer);

							if (tokenizer.nextToken() == StreamTokenizer.TT_WORD) {
								if (tokenizer.sval.equals(EXTENDS_KEYWORD)) {
									if (tokenizer.nextToken() == StreamTokenizer.TT_WORD) {
										extendsClassName = tokenizer.sval;
										skipGeneric(tokenizer);
									}
								} else {
									// If the current token isn't extends, it might be implements -- push it back
									// into the stream so it will be ready to be taken out of the stream again below.
									tokenizer.pushBack();
								}

								if (analyzeInterfaces) {
									if (tokenizer.nextToken() == StreamTokenizer.TT_WORD &&
											tokenizer.sval.equals(IMPLEMENTS_KEYWORD)) {
										boolean		parsingInterfaces = true;

										implementsInterfaceNames = new ArrayList<String>();

										while (parsingInterfaces) {
											if (tokenizer.nextToken() == StreamTokenizer.TT_WORD) {
												implementsInterfaceNames.add(tokenizer.sval);
												skipGeneric(tokenizer);
											} else {
												parsingInterfaces = false;
											}

											if (tokenizer.nextToken() != COMMA_TOKEN) {
												parsingInterfaces = false;
											}
										}
									}
								}
							}

							boolean		isNotInner = className.equals(sourceName);

							if (analyzeInnerClasses || isNotInner) {
								newClass(isPublic, isAbstract, isFinal, (isNotInner ? null : sourceName),
										className, extendsClassName, implementsInterfaceNames, x, y);
							}
						}
					}

					foundCount++;
					isPublic = false;
					isAbstract = false;
					isFinal = false;
				} else if (tokenizer.sval.equals(INTERFACE_KEYWORD)) {
					if (analyzeInterfaces) {
						if (tokenizer.nextToken() == StreamTokenizer.TT_WORD) {
							String				interfaceName = tokenizer.sval;
							ArrayList<String>	extendsInterfaceNames = null;

							skipGeneric(tokenizer);

							if (tokenizer.nextToken() == StreamTokenizer.TT_WORD &&
									tokenizer.sval.equals(EXTENDS_KEYWORD)) {
								boolean		parsingInterfaces = true;

								extendsInterfaceNames = new ArrayList<String>();

								while (parsingInterfaces) {
									if (tokenizer.nextToken() == StreamTokenizer.TT_WORD) {
										extendsInterfaceNames.add(tokenizer.sval);
										skipGeneric(tokenizer);
									} else {
										parsingInterfaces = false;
									}

									if (tokenizer.nextToken() != COMMA_TOKEN) {
										parsingInterfaces = false;
									}
								}
							}

							boolean		isNotInner = interfaceName.equals(sourceName);

							if (analyzeInnerInterfaces || isNotInner) {
								newInterface(isPublic, isAbstract, isFinal, (isNotInner ? null : sourceName),
										interfaceName, extendsInterfaceNames, x, y);
							}
						}
					}

					foundCount++;
					isPublic = false;
					isAbstract = false;
					isFinal = false;
				}
			}
		}

		return foundCount;
	}


	protected void skipGeneric(StreamTokenizer tokenizer) throws java.io.IOException {
		// Skip over generic (eg <E> or <K,V> or <Map.Entry<K,V>>) if present.

		if (tokenizer.nextToken() != LEFT_ANGLE_TOKEN) {
			tokenizer.pushBack();
		} else {
			int		depth = 1;

			while ((tokenizer.nextToken() != RIGHT_ANGLE_TOKEN) || depth > 1) {
				if (tokenizer.ttype == LEFT_ANGLE_TOKEN) {
					depth += 1;
				} else if (tokenizer.ttype == RIGHT_ANGLE_TOKEN) {
					depth -= 1;
				}
			}
		}
	}


	protected int parseClassFile(DataInputStream stream, String filename, int mask, double x, double y) throws java.io.IOException {
		// Open and read the file, looking for declarations of Java classes or interfaces.
		int					foundCount = 0;
		boolean				analyzeClasses = (mask & CLASSES_MASK) == CLASSES_MASK,
							analyzeInnerClasses = (mask & INNER_CLASSES_MASK) == INNER_CLASSES_MASK,
							analyzeInterfaces = (mask & INTERFACES_MASK) == INTERFACES_MASK,
							analyzeInnerInterfaces = (mask & INNER_INTERFACES_MASK) == INNER_INTERFACES_MASK;

		if (DEBUG) {
			Log.info("parseClassFile() analyzing " + filename + " for" +
				(analyzeClasses ? " classes" + (analyzeInnerClasses ? " (including inner classes)" : "") : "") +
				(analyzeInterfaces ? (analyzeClasses ? "," : "") + " interfaces" + ((analyzeInnerInterfaces ? " (including inner interfaces)" : "")) : "") + ".");
		}

		ClassInfo			classInfo = new ClassInfo(stream);

		if (classInfo.isJavaClass) {
			ConstantPool	pool = new ConstantPool(stream);
			AccessFlags		accessFlags = new AccessFlags(stream);
			ClassRef		classRef = new ClassRef(stream, pool);
			SuperclassRef	superclassRef = new SuperclassRef(stream, pool);
			Interfaces		interfaces = new Interfaces(stream, pool);
			FieldInfo		fieldInfo = new FieldInfo(stream);
			MethodInfo		methodInfo = new MethodInfo(stream);
			AttributeInfo	attributeInfo = new AttributeInfo(stream, pool);

			String			classNameWithoutPackage = classRef.nameWithoutPackage,
							sourceName = attributeInfo.sourceName;
			boolean			isNotInner = classNameWithoutPackage.equals(sourceName);

			if (accessFlags.isInterface) {
				if (analyzeInterfaces && (analyzeInnerInterfaces || isNotInner)) {
					newInterface(accessFlags.isPublic, accessFlags.isAbstract, accessFlags.isFinal,
							(isNotInner ? null : sourceName), classRef.name, interfaces.names, x, y);
				}
			} else {
				if (analyzeClasses && (analyzeInnerClasses || isNotInner)) {
					newClass(accessFlags.isPublic, accessFlags.isAbstract, accessFlags.isFinal,
							(isNotInner ? null : sourceName), classRef.name, superclassRef.name,
							(analyzeInterfaces ? interfaces.names : null), x, y);
				}
			}

			foundCount = 1;
		}

		return foundCount;
	}


	protected void displayResults() {
		// Add the nodes and links to the map and apply layouts.
		LWMap		activeMap = VUE.getActiveMap();

		activeMap.addChildren(newComps);

		if (allNodes.size() > 0) {
			LayoutAction.circle.act(allNodes);
			LayoutAction.hierarchical3.act(allNodes);
		}

		VUE.getSelection().setTo(allNodes);
		VUE.getActiveViewer().setZoomFit();

		VUE.getSelection().setTo(newComps);

		VUE.getUndoManager().mark(JAVA_ANALYSIS);
	}


	protected void newClass(boolean isPublic, boolean isAbstract, boolean isFinal, String sourceName, String className,
			String extendsClassName, ArrayList<String> implementsInterfaceNames, double x, double y) {
		StringBuffer	debugMessage = null;

		LWNode		classNode = findOrCreateClassNode(className, x, y);

		if (DEBUG) {
			debugMessage = new StringBuffer("newClass() found " +
					(isPublic ? "public " : "") + (isAbstract ? "abstract " : "") + (isFinal ? "final " : "") +
					"class " + className);
		}

		// This class has now been declared in a file;  set the visual cues to communicate that, and add metadata.
		setIsDeclared(classNode, isPublic, isAbstract, isFinal, sourceName);

		if (extendsClassName != null) {
			LWNode	extendsNode = findOrCreateClassNode(extendsClassName, x, y);

			findOrCreateExtendsLink(classNode, extendsNode);

			if (DEBUG) {
				debugMessage.append(" extends " + extendsNode.label);
			}
		}

		if (implementsInterfaceNames != null) {
			Iterator<String>	iterator = implementsInterfaceNames.iterator();

			if (DEBUG) {
				debugMessage.append(" implements ");
			}

			while (iterator.hasNext()) {
				String	implementsInterfaceName = iterator.next();
				LWNode	implementsNode = findOrCreateInterfaceNode(implementsInterfaceName, x, y);

				findOrCreateImplementsLink(classNode, implementsNode);

				if (DEBUG) {
					debugMessage.append(implementsNode.label + (iterator.hasNext() ? ", " : ""));
				}
			}
		}

		if (DEBUG) {
			debugMessage.append(sourceName != null ? "(declared in " + sourceName + ")." : ".");
			Log.info(debugMessage.toString());
		}
	}


	protected void newInterface(boolean isPublic, boolean isAbstract, boolean isFinal, String sourceName, String interfaceName,
			ArrayList<String> extendsInterfaceNames, double x, double y) {
		StringBuffer	debugMessage = null;

		LWNode		interfaceNode = findOrCreateInterfaceNode(interfaceName, x, y);

		if (DEBUG) {
			debugMessage = new StringBuffer("newInterface() found " +
					(isPublic ? "public " : "") + (isAbstract ? "abstract " : "") + (isFinal ? "final " : "") +
					"interface " + interfaceName);
		}

		// This interface has now been declared in a file;  set the visual cue to communicate that.
		// Note that declaring an interface abstract is redundant, but it is valid Java syntax.
		setIsDeclared(interfaceNode, isPublic, isAbstract, isFinal, sourceName);

		if (extendsInterfaceNames != null) {
			Iterator<String>	iterator = extendsInterfaceNames.iterator();

			if (DEBUG) {
				debugMessage.append(" extends ");
			}

			while (iterator.hasNext()) {
				String	extendsInterfaceName = iterator.next();
				LWNode	extendsNode = findOrCreateInterfaceNode(extendsInterfaceName, x, y);

				findOrCreateExtendsLink(interfaceNode, extendsNode);

				if (DEBUG) {
					debugMessage.append(extendsNode.label + (iterator.hasNext() ? ", " : ""));
				}
			}
		}

		if (DEBUG) {
			debugMessage.append(sourceName != null ? "(declared in " + sourceName + ")." : ".");
			Log.info(debugMessage.toString());
		}
	}


	protected LWNode findOrCreateClassNode(String className, double x, double y) {
		LWNode		classNode = classHash.get(className);

		if (classNode == null) {
			// Create a node to represent the class and place it in the center of the
			// visible part of the map.
			classNode = newNode(className, x, y, CLASS_SHAPE, CLASS_KEYWORD);
		}

		return classNode;
	}


	protected LWNode findOrCreateInterfaceNode(String interfaceName, double x, double y) {
		LWNode		interfaceNode = classHash.get(interfaceName);

		if (interfaceNode == null) {
			// Create a node to represent the interface and place it in the center of the
			// visible part of the map.
			interfaceNode = newNode(interfaceName, x, y, INTERFACE_SHAPE, INTERFACE_KEYWORD);
		}

		return interfaceNode;
	}


	protected LWNode newNode(String nodeName, double x, double y, Class<? extends RectangularShape> shape, String metadata) {
		LWNode			node = new LWNode(nodeName);
		MetadataList	metadataList = node.getMetadataList();

		node.setShape(shape);
		node.setCenterAt(x, y);

		node.setFillColor(Color.WHITE);
		node.setTextColor(UNDECLARED_COLOR);
		node.setStrokeColor(UNDECLARED_COLOR);
		node.mStrokeStyle.setTo(NONABSTRACT_STROKE_STYLE);
		node.mStrokeWidth.setTo(NONFINAL_STROKE_WIDTH);

		metadataList.add(METADATA_CATEGORY, metadata);
		metadataList.add(METADATA_CATEGORY, METADATA_KEYWORD_UNDECLARED);

		node.setToNaturalSize();

		allNodes.add(node);
		newComps.add(node);
		classHash.put(nodeName, node);

		return node;
	}


	protected void setIsDeclared(LWNode node, boolean isPublic, boolean isAbstract, boolean isFinal, String sourceName) {
		node.setStrokeColor(DECLARED_COLOR);
		node.setTextColor(DECLARED_COLOR);

		for (LWLink link : node.getLinks()) {
			if (link.getTail() == node) {
				link.setStrokeColor(DECLARED_COLOR);
				link.setTextColor(DECLARED_COLOR);
			}
		}

		MetadataList	metadataList = node.getMetadataList();

		metadataList.removeCategoryType(METADATA_CATEGORY, METADATA_KEYWORD_UNDECLARED);

		if (isPublic && !metadataList.contains(METADATA_CATEGORY, PUBLIC_KEYWORD)) {
			metadataList.add(METADATA_CATEGORY, PUBLIC_KEYWORD);		
		}

		if (isAbstract && !metadataList.contains(METADATA_CATEGORY, ABSTRACT_KEYWORD)) {
			node.mStrokeStyle.setTo(ABSTRACT_STROKE_STYLE);
			metadataList.add(METADATA_CATEGORY, ABSTRACT_KEYWORD);
		}

		if (isFinal && !metadataList.contains(METADATA_CATEGORY, FINAL_KEYWORD)) {
			node.mStrokeWidth.setTo(FINAL_STROKE_WIDTH);
			metadataList.add(METADATA_CATEGORY, FINAL_KEYWORD);
		}

		if (sourceName != null) {
			metadataList.add(METADATA_CATEGORY, METADATA_KEYWORD_DECLARED + sourceName);
		}
	}


	protected LWLink findOrCreateExtendsLink(LWNode node, LWNode parentNode) {
		return findOrCreateLink(node, parentNode, EXTENDS_KEYWORD);
	}


	protected LWLink findOrCreateImplementsLink(LWNode node, LWNode parentNode) {
		return findOrCreateLink(node, parentNode, IMPLEMENTS_KEYWORD);
	}


	protected LWLink findOrCreateLink(LWNode node, LWNode parentNode, String label) {
		LWLink		link = null;

		for (LWLink existingLink : node.getLinks()) {
			if (existingLink.hasEndpoint(parentNode) && existingLink.getLabel().equals(label)) {
				link = existingLink;
				break;
			}
		}

		if (link == null) {
			Color	parentNodeColor = parentNode.getStrokeColor();

			// Locate the node under its parent to avoid a warning from LWLink code.
			node.setY(parentNode.getY() + (2 * parentNode.getHeight()));

			link = new LWLink(node, parentNode);

			link.setLabel(label);
			link.setStrokeColor(parentNodeColor);
			link.setTextColor(parentNodeColor);

			newComps.add(link);
		}

		return link;
	}


	protected File[] showFileChooser() {
		File				files[] = null;
		VueFileChooser		chooser = VueFileChooser.getVueFileChooser();

		try {
			chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
			chooser.setFileFilter(new JavaSourceFileFilter());
			chooser.setFileFilter(new JavaClassFileFilter());
			chooser.setFileFilter(new JavaArchiveFileFilter());
			chooser.setMultiSelectionEnabled(true);
			chooser.setDialogTitle(CHOOSE_FILES);
			chooser.setApproveButtonText(ANALYZE);
			chooser.setApproveButtonToolTipText(ANALYZE_TOOLTIP);

			if (lastDirectory != null) {
				chooser.setCurrentDirectory(lastDirectory);
			}

			if (chooser.showOpenDialog(VUE.getDialogParent()) == JFileChooser.APPROVE_OPTION) {
				files = chooser.getSelectedFiles();
				currentFileFilter = (FileFilter)chooser.getFileFilter();

				File		parentFile = files[0].getParentFile();

				if (parentFile.isDirectory()) {
					lastDirectory = parentFile;
				}
			}
		} catch(Exception ex) {
			ex.printStackTrace();
			VueUtil.alert(ex.getMessage(), JAVA_ANALYSIS_ERROR);
		} finally {
			chooser.setDialogTitle(DEFAULT_FILE_CHOOSER_TITLE);
			chooser.setApproveButtonText(DEFAULT_FILE_CHOOSER_OPEN_BUTTON);
			chooser.setApproveButtonToolTipText(DEFAULT_FILE_CHOOSER_OPEN_BUTTON_TOOLTIP);
		}

		return files;
	}


	// ActionListener method
	public void actionPerformed(ActionEvent event) {
		JComponent		source = (JComponent) event.getSource();

		if (source == classesCheckBox || source == interfacesCheckBox) {
			analyzeButton.setEnabled(classesCheckBox.isSelected() || interfacesCheckBox.isSelected());

			if (source == classesCheckBox) {
				innerClassesCheckBox.setEnabled(classesCheckBox.isSelected());
			} else if (source == interfacesCheckBox) {
				innerInterfacesCheckBox.setEnabled(interfacesCheckBox.isSelected());
			}
		} else if (source == analyzeButton) {
			int		analysisMask = (classesCheckBox.isSelected() ? CLASSES_MASK : 0) |
						(innerClassesCheckBox.isEnabled() && innerClassesCheckBox.isSelected() ? INNER_CLASSES_MASK : 0) |
						(interfacesCheckBox.isSelected() ? INTERFACES_MASK : 0) |
						(innerInterfacesCheckBox.isEnabled() && innerInterfacesCheckBox.isSelected() ? INNER_INTERFACES_MASK : 0);

			if (analysisThread == null) {
				analysisThread = new AnalysisThread(analysisMask);
				proceedWithAnalysis = true;
				analysisThread.start();
			} else {
				messageTextArea.setText("");
				analyzeButton.setText(CHOOSE_FILES);
				proceedWithAnalysis = false;
				analysisThread.interrupt();
				analysisThread = null;
			}
		}
	}


	protected static void addToGridBag(Container container, Component component,
			int gridX, int gridY, int gridWidth, int gridHeight,
			int anchor, int fill, double weightX, double weightY,
			Insets insets) {
		GridBagConstraints	constraints = new GridBagConstraints(gridX, gridY, gridWidth, gridHeight,
				weightX, weightY, anchor, fill, insets, 0, 0) ;

		((GridBagLayout)container.getLayout()).setConstraints(component, constraints);
		container.add(component);
	}


	private class AnalysisThread extends Thread {
		int		analysisMask = 0;


		public AnalysisThread(int analysisMask) {
			super();

			this.analysisMask = analysisMask;
		}


		public void run() {
			try {
				analyze(analysisMask);
			} catch(Exception ex) {
				ex.printStackTrace();
				VueUtil.alert(ex.getMessage(), JAVA_ANALYSIS_ERROR);
			}
			finally
			{
				analysisThread = null;
			}
		}
	}


	private class ClassInfo {
		protected static final int	JAVA_CLASS_FILE_MAGIC_NUMBER = 0xCAFEBABE;
		public boolean				isJavaClass = false;

		ClassInfo(DataInputStream stream) throws java.io.IOException {
			int					magicNumber = stream.readInt();

			isJavaClass = magicNumber == JAVA_CLASS_FILE_MAGIC_NUMBER;

			if (DEBUG) {
				Log.info("magicNumber " + magicNumber + " is " + (isJavaClass ? "" : "NOT ") + "java magic number.");
			}

			if (isJavaClass) {
				short			compilerMinorVersion = stream.readShort(),
								compilerMajorVersion = stream.readShort();
				if (DEBUG) {
					Log.info("compilerMinorVersion is " + compilerMinorVersion);
					Log.info("compilerMajorVersion is " + compilerMajorVersion);
				}
			}
		}
	}


	private class ConstantPool {
		public ConstantPoolItem			items[];

		ConstantPool(DataInputStream stream) throws java.io.IOException {
			short	constantPoolCount = (short)(stream.readShort() - 1);

			if (DEBUG) {
				Log.info("constantPoolCount is " + constantPoolCount);
			}

			// Indecies into the constant pool are one-based, so make the array
			// one larger than the number of items and put the first item at [1].
			items = new ConstantPoolItem[constantPoolCount + 1];

			for (int index = 1; index <= constantPoolCount; index++) {
				byte				type = stream.readByte();
				ConstantPoolItem	newItem = null;

				switch (type) {
				case 1:
					newItem = new ConstantPoolItemUTF8(stream);
					break;

				case 2:
					newItem = new ConstantPoolItemUnicode(stream);
					break;

				case 3:
					newItem = new ConstantPoolItemInteger(stream);
					break;

				case 4:
					newItem = new ConstantPoolItemFloat(stream);
					break;

				case 5:
					newItem = new ConstantPoolItemLong(stream);
					break;

				case 6:
					newItem = new ConstantPoolItemDouble(stream);
					break;

				case 7:
					newItem = new ConstantPoolItemClass(stream);
					break;

				case 8:
					newItem = new ConstantPoolItemString(stream);
					break;

				case 9:
					newItem = new ConstantPoolItemFieldRef(stream);
					break;

				case 10:
					newItem = new ConstantPoolItemMethodRef(stream);
					break;

				case 11:
					newItem = new ConstantPoolItemInterfaceMethodRef(stream);
					break;

				case 12:
					newItem = new ConstantPoolItemNameAndType(stream);
					break;

				default:
					VueUtil.alert("Unknown constant pool type: '" + type + "'", JAVA_ANALYSIS_ERROR);
				}

				items[index] = newItem;

				if (DEBUG) {
					Log.info("          ConstantPool[" + index + "] (type " + type + ") is " + newItem.toString());
				}

				if (type == 5 || type == 6) {
					// Long and double items are entered in the constant pool twice.
					// It's totally unclear why this is so.  That's just the way it is.
					index++;
					items[index] = newItem;
				}
			}
		}
	}


	private abstract class ConstantPoolItem {
	}


	private class ConstantPoolItemUTF8 extends ConstantPoolItem {
		public String	value = null;

		ConstantPoolItemUTF8(DataInputStream stream) throws java.io.IOException {
			short	length = stream.readShort();
			byte	bytes[] = new byte[length];

			for (int index = 0; index < length; index++) {
				bytes[index] = stream.readByte();
			}

			value = new String(bytes);
		}


		public String toString() {
			return value;
		}
	}


	private class ConstantPoolItemUnicode extends ConstantPoolItem {
		public String	value = null;

		ConstantPoolItemUnicode(DataInputStream stream) throws java.io.IOException {
			short	length = stream.readShort();
			byte	bytes[] = new byte[length];

			for (int index = 0; index < length; index++) {
				bytes[index] = stream.readByte();
			}

			value = new String(bytes);
		}


		public String toString() {
			return value;
		}
	}


	private class ConstantPoolItemInteger extends ConstantPoolItem {
		public int		value;

		ConstantPoolItemInteger(DataInputStream stream) throws java.io.IOException {
			value = stream.readInt();
		}


		public String toString() {
			return Integer.toString(value);
		}
	}


	private class ConstantPoolItemFloat extends ConstantPoolItem {
		public float	value;

		ConstantPoolItemFloat(DataInputStream stream) throws java.io.IOException {
			value = stream.readFloat();
		}


		public String toString() {
			return Float.toString(value);
		}
	}


	private class ConstantPoolItemLong extends ConstantPoolItem {
		public long		value;

		ConstantPoolItemLong(DataInputStream stream) throws java.io.IOException {
			value = stream.readLong();
		}


		public String toString() {
			return Long.toString(value);
		}
	}


	private class ConstantPoolItemDouble extends ConstantPoolItem {
		public double	value;

		ConstantPoolItemDouble(DataInputStream stream) throws java.io.IOException {
			value = stream.readDouble();
		}


		public String toString() {
			return Double.toString(value);
		}
	}


	private class ConstantPoolItemClass extends ConstantPoolItem {
		public short	nameIndex;

		ConstantPoolItemClass(DataInputStream stream) throws java.io.IOException {
			nameIndex = stream.readShort();
		}


		public String toString() {
			return Short.toString(nameIndex);
		}
	}


	private class ConstantPoolItemString extends ConstantPoolItem {
		public short	stringIndex;

		ConstantPoolItemString(DataInputStream stream) throws java.io.IOException {
			stringIndex = stream.readShort();
		}


		public String toString() {
			return Short.toString(stringIndex);
		}
	}


	private class ConstantPoolItemFieldRef extends ConstantPoolItem {
		public short	classIndex,
						nameAndTypeIndex;

		ConstantPoolItemFieldRef(DataInputStream stream) throws java.io.IOException {
			classIndex = stream.readShort();
			nameAndTypeIndex = stream.readShort();
		}


		public String toString() {
			return "classIndex " + Short.toString(classIndex) + ", nameAndTypeIndex " + Short.toString(nameAndTypeIndex);
		}
	}


	private class ConstantPoolItemMethodRef extends ConstantPoolItem {
		public short	classIndex,
						nameAndTypeIndex;

		ConstantPoolItemMethodRef(DataInputStream stream) throws java.io.IOException {
			classIndex = stream.readShort();
			nameAndTypeIndex = stream.readShort();
		}


		public String toString() {
			return "classIndex " + Short.toString(classIndex) + ", nameAndTypeIndex " + Short.toString(nameAndTypeIndex);
		}
	}


	private class ConstantPoolItemInterfaceMethodRef extends ConstantPoolItem {
		public short	classIndex,
						nameAndTypeIndex;

		ConstantPoolItemInterfaceMethodRef(DataInputStream stream) throws java.io.IOException {
			classIndex = stream.readShort();
			nameAndTypeIndex = stream.readShort();
		}


		public String toString() {
			return "classIndex " + Short.toString(classIndex) + ", nameAndTypeIndex " + Short.toString(nameAndTypeIndex);
		}
	}


	private class ConstantPoolItemNameAndType extends ConstantPoolItem {
		public short	nameIndex,
						descriptorIndex;

		ConstantPoolItemNameAndType(DataInputStream stream) throws java.io.IOException {
			nameIndex = stream.readShort();
			descriptorIndex = stream.readShort();
		}


		public String toString() {
			return "nameIndex " + Short.toString(nameIndex) + ", descriptorIndex " + Short.toString(descriptorIndex);
		}
	}


	private class AccessFlags {
		protected static final int	ACC_PUBLIC      = 0x0001,
									ACC_FINAL       = 0x0010,
									ACC_SUPER       = 0x0020,
									ACC_INTERFACE   = 0x0200,
									ACC_ABSTRACT    = 0x0400;
		public boolean				isInterface,
									isPublic,
									isAbstract,
									isFinal;

		AccessFlags(DataInputStream stream) throws java.io.IOException {
			short			accessFlags = stream.readShort();

			isInterface = (accessFlags & ACC_INTERFACE) != 0;
			isPublic = (accessFlags & ACC_PUBLIC) != 0;
			isAbstract = (accessFlags & ACC_ABSTRACT) != 0;
			isFinal = (accessFlags & ACC_FINAL) != 0;

			if (DEBUG) {
				Log.info("isInterface is " + (isInterface ? "true" : "false"));
				Log.info("isPublic is " + (isPublic ? "true" : "false"));
				Log.info("isAbstract is " + (isAbstract ? "true" : "false"));
				Log.info("isFinal is " + (isFinal ? "true" : "false"));
			}
		}
	}


	private class ClassRef {
		public String	name = null,
						nameWithoutPackage = null;

		ClassRef(DataInputStream stream, ConstantPool pool) throws java.io.IOException {
			short		classIndex = stream.readShort();

			ConstantPoolItemClass	classItem = (ConstantPoolItemClass)pool.items[classIndex];
			ConstantPoolItemUTF8	classNameItem =	(ConstantPoolItemUTF8)pool.items[classItem.nameIndex];

			name = classNameItem.value.replace('/', '.');

			int			packageNameLength = name.lastIndexOf('.') + 1;

			nameWithoutPackage = (packageNameLength > 0 ? name.substring(packageNameLength) : name);

			if (DEBUG) {
				Log.info("Class index is " + classIndex + " and name is " + name);
			}
		}
	}


	private class SuperclassRef {
		public String	name = null;

		SuperclassRef(DataInputStream stream, ConstantPool pool) throws java.io.IOException {
			short classIndex = stream.readShort();

			ConstantPoolItemClass	classItem = (ConstantPoolItemClass)pool.items[classIndex];
			ConstantPoolItemUTF8	classNameItem =	(ConstantPoolItemUTF8)pool.items[classItem.nameIndex];

			name = classNameItem.value.replace('/', '.');

			if (DEBUG) {
				Log.info("Superclass index is " + classIndex + " and name is " + name);
			}
		}
	}


	private class Interfaces {
		public ArrayList<String>	names = null;

		Interfaces(DataInputStream stream, ConstantPool pool) throws java.io.IOException {
			short			interfaceCount = stream.readShort();

			if (DEBUG) {
				Log.info("interfaceCount is " + interfaceCount);
			}

			if (interfaceCount > 0) {
				names = new ArrayList<String>(interfaceCount);

				for (int index = 0; index < interfaceCount; index++) {
					short					interfaceIndex = stream.readShort();
					ConstantPoolItemClass	interfaceItem = (ConstantPoolItemClass)pool.items[interfaceIndex];
					ConstantPoolItemUTF8	interfaceNameItem =	(ConstantPoolItemUTF8)pool.items[interfaceItem.nameIndex];
					String					interfaceName = interfaceNameItem.value.replace('/', '.');

					names.add(index, interfaceName);

					if (DEBUG) {
						Log.info("interfaceName[" + index + "] is " + interfaceName);
					}
				}
			}
		}
	}


	private class FieldInfo {
		// Note that field info is currently unused by JavaAnalysisPanel, so it's read
		// and discarded in order to get to the AttributeInfo that is needed.

		FieldInfo(DataInputStream stream) throws java.io.IOException {
			short			fieldCount = stream.readShort();

			if (DEBUG) {
				Log.info("fieldCount is " + fieldCount);
			}

			for (int fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
				short			accessFlags = stream.readShort(),
								nameIndex = stream.readShort(),
								descriptorIndex = stream.readShort(),
								attributeCount = stream.readShort();

				for (int attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
					short		attributeNameIndex = stream.readShort();
					int			attributeLength = stream.readInt();

					for (int infoIndex = 0; infoIndex < attributeLength; infoIndex++) {
						byte	info = stream.readByte();
					}
				}
			}
		}
	}


	private class MethodInfo {
		// Note that method info is currently unused by JavaAnalysisPanel, so it's read
		// and discarded in order to get to the AttributeInfo that is needed.
		MethodInfo(DataInputStream stream) throws java.io.IOException {
			short			methodCount = stream.readShort();

			if (DEBUG) {
				Log.info("methodCount is " + methodCount);
			}

			for (int methodIndex = 0; methodIndex < methodCount; methodIndex++) {
				short			accessFlags = stream.readShort(),
								nameIndex = stream.readShort(),
								descriptorIndex = stream.readShort(),
								attributeCount = stream.readShort();

				for (int attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
					short		attributeNameIndex = stream.readShort();
					int			attributeLength = stream.readInt();

					for (int infoIndex = 0; infoIndex < attributeLength; infoIndex++) {
						byte	info = stream.readByte();
					}
				}
			}
		}
	}


	private class AttributeInfo {
		// Note that the only attribute currently used by JavaAnalysisPanel is SourceFile, so
		// other attributes are read and discarded.
		
		protected static final String	ATTRIBUTE_SOURCE_FILE = "SourceFile";
		public String					sourceName = null;

		AttributeInfo(DataInputStream stream, ConstantPool pool) throws java.io.IOException {
			short			attributeCount = stream.readShort();

			if (DEBUG) {
				Log.info("attributeCount is " + attributeCount);
			}

			for (int attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
				short		nameIndex = stream.readShort();
				int			length = stream.readInt();

				if (((ConstantPoolItemUTF8)pool.items[nameIndex]).value.equals(ATTRIBUTE_SOURCE_FILE)) {
					short					sourceFileIndex = stream.readShort();
					ConstantPoolItemUTF8	sourceNameItem = (ConstantPoolItemUTF8)pool.items[sourceFileIndex];

					sourceName = sourceNameItem.value.replace(JavaSourceFileFilter.FILE_EXTENSION, "");

					if (DEBUG) {
						Log.info("sourceName is " + sourceName);
					}

					// Any remaining attributes are currently unused, so don't bother reading them.
						break;
				} else {
					for (int infoIndex = 0; infoIndex < length; infoIndex++) {
						byte	info = stream.readByte();
					}
				}
			}
		}
	}


	protected class JavaSourceFileFilter extends javax.swing.filechooser.FileFilter implements FileFilter {
		// Note that either extending javax.swing.filechooser.FileFilter or implementing
		// java.io.FileFilter should suffice, except that the javax.swing.filechooser.setFileFilter
		// method insists on an argument of type javax.swing.filechooser.FileFilter, which, perversely,
		// doesn't implement FileFilter, and the java.io.File.listFile method more correctly wants as an
		// argument a class that implements FileFilter.  Therefore, this class does both.
		public static final String		FILE_EXTENSION = ".java";


		public boolean accept(File file) {
			return file.isDirectory() || file.getName().toLowerCase().endsWith(FILE_EXTENSION);
		}


		public String getDescription() {
			return FILE_EXTENSION;
		}
	}


	protected class JavaClassFileFilter extends javax.swing.filechooser.FileFilter implements FileFilter {
		// Note that either extending javax.swing.filechooser.FileFilter or implementing
		// java.io.FileFilter should suffice, except that the javax.swing.filechooser.setFileFilter
		// method insists on an argument of type javax.swing.filechooser.FileFilter, which, perversely,
		// doesn't implement FileFilter, and the java.io.File.listFile method more correctly wants as an
		// argument a class that implements FileFilter.  Therefore, this class does both.
		public static final String		FILE_EXTENSION = ".class";


		public boolean accept(File file) {
			return file.isDirectory() || file.getName().toLowerCase().endsWith(FILE_EXTENSION);
		}


		public String getDescription() {
			return FILE_EXTENSION;
		}
	}


	protected class JavaArchiveFileFilter extends javax.swing.filechooser.FileFilter implements FileFilter {
		// Note that either extending javax.swing.filechooser.FileFilter or implementing
		// java.io.FileFilter should suffice, except that the javax.swing.filechooser.setFileFilter
		// method insists on an argument of type javax.swing.filechooser.FileFilter, which, perversely,
		// doesn't implement FileFilter, and the java.io.File.listFile method more correctly wants as an
		// argument a class that implements FileFilter.  Therefore, this class does both.
		public static final String		FILE_EXTENSION = ".jar";


		public boolean accept(File file) {
			return file.isDirectory() || file.getName().toLowerCase().endsWith(FILE_EXTENSION);
		}


		public String getDescription() {
			return FILE_EXTENSION;
		}
	}
}
