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; } } }