package com.francetelecom.rd.stubs.engine; /* * #%L * Matos * $Id:$ * $HeadURL:$ * %% * Copyright (C) 2008 - 2014 Orange SA * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Properties; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.WindowConstants; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.AbstractLayoutCache; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; /** * A swing tree that shows what has been achieved for annotations. * @author Pierre Cregut * */ @SuppressWarnings("serial") public class JClassView extends JTree { final static String COMMAND_KEY = "launchEditor"; private final static int WIDTH= 800; private final static int HEIGHT= 600; private final static int WIDTH_MIN = 300; private final static int HEIGHT_MIN= 350; private static final int VERSION_CELL_WIDTH = 100; private static final int VERSION_CELL_HEIGHT = 14; String annotationPackage; /** * Table that map packages to the sub package they "contain" (intuitive meaning if not formal). */ HashMap<String, HashSet<String>> subPackages = new HashMap<String,HashSet<String>> (); /** * Table that map package to the classes they contain. */ HashMap<String, HashSet<Class<?>>> packageContents = new HashMap<String,HashSet<Class<?>>> (); static VersionDatabase database; private static Component legend; private String command; /** * Reimplementation of BasicTreeUI to make JTree take all the space * available for its width. * @author Laurent Sebag * */ public static class ExpandedTreeUI extends BasicTreeUI { @Override protected void installListeners() { super.installListeners(); tree.addComponentListener(componentListener); } @Override protected void uninstallListeners() { tree.removeComponentListener(componentListener); super.uninstallListeners(); } @Override protected AbstractLayoutCache.NodeDimensions createNodeDimensions() { return new NodeDimensionsHandler() { @Override public Rectangle getNodeDimensions(Object value, int row, int depth, boolean expanded, Rectangle size) { Rectangle dimensions = super.getNodeDimensions(value, row, depth, expanded, size); Insets insets = tree.getInsets(); dimensions.width = tree.getWidth() - getRowX(row, depth) - insets.left - insets.right; return dimensions; } }; } private final ComponentListener componentListener = new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { treeState.invalidateSizes(); tree.repaint(); }; }; } /** * Tells how to display each row in the tree. * @author Pierre Cregut * */ private static class Renderer extends DefaultTreeCellRenderer { private static final long serialVersionUID = 1L; private final ArrayList<String> versions; private JPanel renderer = new JPanel(); private JLabel main = new JLabel(); private JPanel annotPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); private Box versionPanel = Box.createHorizontalBox(); public Renderer() { renderer.setLayout(new BoxLayout(renderer, BoxLayout.LINE_AXIS)); renderer.add(main); renderer.add(annotPanel); renderer.add(Box.createHorizontalStrut(10)); renderer.add(Box.createHorizontalGlue()); renderer.add(versionPanel); renderer.add(Box.createHorizontalStrut(10)); renderer.setBorder(BorderFactory.createEmptyBorder()); annotPanel.setBorder(BorderFactory.createEmptyBorder()); main.setBorder(BorderFactory.createEmptyBorder()); main.setFont(super.getFont()); versions = new ArrayList<String>(); List<String> rawVersions = database.getVersion(); for(String v : rawVersions) { String version = v.substring(1); if(!versions.contains(version)) versions.add(version); } } @Override public Component getTreeCellRendererComponent( JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (value instanceof Node) { Node node = (Node) value; setColor(node.getColor()); } else { setColor(Color.black); } if (value instanceof Node) { Node node = (Node) value; main.setIcon(node.getIcon()); main.setText(node.toString()); annotPanel.removeAll(); for(Icon i : node.getIcons()) annotPanel.add(new JLabel(i)); versionPanel.removeAll(); for(String v : versions) { JComponent versionLabel; if( row == 0 ) { versionLabel = new JLabel(v); versionLabel.setBorder(BorderFactory.createMatteBorder(1, 1, 2, 1, Color.black )); versionLabel.setBackground( Color.white ); } else { versionLabel = new JPanel(); if(selected) versionLabel.setBorder(BorderFactory.createMatteBorder(2, 1, 2, 1, Color.black )); else versionLabel.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.black )); versionLabel.setBackground( node.getColorForVersion(v) ); } versionLabel.setOpaque(true); versionLabel.setPreferredSize( new Dimension(VERSION_CELL_WIDTH, VERSION_CELL_HEIGHT) ); versionLabel.setMinimumSize( new Dimension(VERSION_CELL_WIDTH, VERSION_CELL_HEIGHT) ); versionLabel.setMaximumSize( new Dimension(VERSION_CELL_WIDTH, VERSION_CELL_HEIGHT) ); versionPanel.add(versionLabel); versionPanel.add(Box.createHorizontalStrut(1)); } renderer.setBackground(selected ? backgroundSelectionColor : node.getDepthColor()); annotPanel.setBackground(selected ? backgroundSelectionColor : node.getDepthColor()); versionPanel.setBackground(selected ? backgroundSelectionColor : node.getDepthColor()); } else { renderer.setBackground(selected ? backgroundSelectionColor : backgroundNonSelectionColor); annotPanel.setBackground(selected ? backgroundSelectionColor : backgroundNonSelectionColor); versionPanel.setBackground(selected ? backgroundSelectionColor : backgroundNonSelectionColor); main.setIcon(null); main.setText(value.toString()); } renderer.setEnabled(tree.isEnabled()); return renderer; } private void setColor(Color c) { main.setForeground(c); } } /** * How to react to mouse event (use button 2 so we do not rely on the tree event listener * interface) * @author Pierre Cregut * */ transient private MouseListener ml = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { int selRow = getRowForLocation(e.getX(), e.getY()); TreePath selPath = getPathForLocation(e.getX(), e.getY()); if(selRow != -1) { if(e.getButton() == MouseEvent.BUTTON2) { launch(selPath); } } } }; ReflexUtil rf; /** * Builds the tree widget. Takes various properties as argument. * @param prefix * @param properties */ public JClassView(ReflexUtil rf, Properties properties) { this.rf = rf; command = properties.getProperty(COMMAND_KEY); setCellRenderer(new Renderer()); // hack to be able to draw a table next to the tree, right aligned. setUI(new ExpandedTreeUI()); addMouseListener(ml); } /** * Register a class in the hierarchy displayed by the tree. * @param clazz */ public void register(Class<?> clazz) { if (clazz.isMemberClass() || clazz.isAnonymousClass()) return; String pkgName = ReflexUtil.getPackageName(clazz); if (pkgName == null) { throw new RuntimeException("null package name"); } pkgName = rf.restoreString(pkgName); add(packageContents, pkgName, clazz); registerPackage(pkgName); } /** * Auxiliary method to register subpackage relations. * @param pkgName */ private void registerPackage(String pkgName) { int p = pkgName.lastIndexOf('.'); if (p > 0) { String parent = pkgName.substring(0,p); add(subPackages, parent, pkgName); registerPackage(parent); } else { if (pkgName.length() > 0) add(subPackages,"",pkgName); } } /** * Utility method to add an element to a multimap * @param <K> type of key * @param <V> type of value * @param map map that map an element to one or more values (in a set) * @param key key * @param value value */ private static <K,V> void add(HashMap<K, HashSet<V>> map, K key, V value) { HashSet <V> cell = map.get(key); if (cell == null) { cell = new HashSet<V>(); map.put(key, cell); } cell.add(value); } /** * Init the model in the tree when all the classes are registered. */ public void init() { Node root = new Node(this, "", -1); TreeModel model = new DefaultTreeModel(root); this.setModel(model); } /** * Launch an editor on a class. * @param selPath * @return */ private int launch(TreePath selPath) { Node c = (Node) selPath.getLastPathComponent(); Object content = c.getContent(); if (content instanceof Class<?>) { String path = rf.restoreString(((Class <?>) content).getName().replace('.', '/')); if (path.contains("$")) return -1; String args = command.replace("$$", path); Runtime runtime = Runtime.getRuntime(); try { Process p = runtime.exec(args); return p.waitFor(); } catch (Exception e) { return 1; } } else return -1; } /** * Generate the view containing the class tree. * @param hierarchy The hierarchy of observed classes. * @param rf a relocation context. * @param properties properties (contain the command name). * @param database */ public static void viewStat(Hierarchy hierarchy, ReflexUtil rf, Properties properties, VersionDatabase db) { final JFrame frame = new JFrame("stubs"); database = db; ActionListener actionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { frame.dispose(); } }; ItemListener itemListener = new ItemListener() { boolean showLegend = false; @Override public void itemStateChanged(ItemEvent e) { showLegend = !showLegend; legend.setVisible(showLegend); System.out.println("show legend: "+ (showLegend?"yes":"no") ); } }; JClassView tree = new JClassView(rf,properties); for(Class <?> clazz : hierarchy.getContents()) {tree.register(clazz);} tree.init(); frame.setDefaultCloseOperation(JDialog.EXIT_ON_CLOSE); frame.setIconImage(new ImageIcon(JClassView.class.getResource("icons/annotation.png")).getImage()); frame.setMinimumSize(new Dimension(WIDTH_MIN,HEIGHT_MIN)); frame.setPreferredSize(new Dimension(WIDTH,HEIGHT)); JScrollPane scrollPane = new JScrollPane(tree); legend = createLegend(); Box contentPane = new Box(BoxLayout.PAGE_AXIS); contentPane.add(scrollPane); contentPane.add(legend); legend.setVisible(false); frame.setContentPane(contentPane); frame.setJMenuBar(createMenuBar(actionListener, itemListener)); frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); frame.pack(); frame.setVisible(true); } private static Component createLegend() { Box hBox = new Box(BoxLayout.LINE_AXIS); Box hHBox; Box vBox; vBox = new Box(BoxLayout.PAGE_AXIS); // color desc line hHBox = new Box(BoxLayout.LINE_AXIS); JLabel label = new JLabel(" "); label.setBackground(Node.PACKAGE_VERSION_COLORS.allPresentandVisible); label.setOpaque(true); hHBox.add(label); hHBox.add(Box.createHorizontalGlue()); hHBox.add(new JLabel("All elements are present and visible")); vBox.add(hHBox); vBox.add(Box.createVerticalStrut(15)); // color desc line hHBox = new Box(BoxLayout.LINE_AXIS); label = new JLabel(" "); label.setBackground(Node.PACKAGE_VERSION_COLORS.allPresentAndHidden); label.setOpaque(true); hHBox.add(label); hHBox.add(Box.createHorizontalGlue()); hHBox.add(new JLabel("All elements are present and hidden")); vBox.add(hHBox); vBox.add(Box.createVerticalStrut(15)); // color desc line hHBox = new Box(BoxLayout.LINE_AXIS); label = new JLabel(" "); label.setBackground(Node.PACKAGE_VERSION_COLORS.allPresentSomeHidden); label.setOpaque(true); hHBox.add(label); hHBox.add(Box.createHorizontalGlue()); hHBox.add(new JLabel("All elements are present but some are hidden")); vBox.add(hHBox); vBox.add(Box.createVerticalStrut(15)); hBox.add(vBox); hBox.add(Box.createHorizontalGlue()); vBox = new Box(BoxLayout.PAGE_AXIS); // color desc line hHBox = new Box(BoxLayout.LINE_AXIS); label = new JLabel(" "); label.setBackground(Node.PACKAGE_VERSION_COLORS.someElementsNotPresentButAllVisible); label.setOpaque(true); hHBox.add(label); hHBox.add(Box.createHorizontalGlue()); hHBox.add(new JLabel("Some elements are not present but all are visible")); vBox.add(hHBox); vBox.add(Box.createVerticalStrut(15)); // color desc line hHBox = new Box(BoxLayout.LINE_AXIS); label = new JLabel(" "); label.setBackground(Node.PACKAGE_VERSION_COLORS.someElementsNotPresentButAllHidden); label.setOpaque(true); hHBox.add(label); hHBox.add(Box.createHorizontalGlue()); hHBox.add(new JLabel("Some elements are not present but all are hidden")); vBox.add(hHBox); vBox.add(Box.createVerticalStrut(15)); // color desc line hHBox = new Box(BoxLayout.LINE_AXIS); label = new JLabel(" "); label.setBackground(Node.PACKAGE_VERSION_COLORS.someElementsNotPresent); label.setOpaque(true); hHBox.add(label); hHBox.add(Box.createHorizontalGlue()); hHBox.add(new JLabel("Some elements are not present (both visible and hidden)")); vBox.add(hHBox); vBox.add(Box.createVerticalStrut(15)); // color desc line hHBox = new Box(BoxLayout.LINE_AXIS); label = new JLabel(" "); label.setBackground(Node.PACKAGE_VERSION_COLORS.noElementsPresent); label.setOpaque(true); hHBox.add(label); hHBox.add(Box.createHorizontalGlue()); hHBox.add(new JLabel("No elements are present")); vBox.add(hHBox); hBox.add(vBox); return hBox; } private static JMenuBar createMenuBar(ActionListener aL, ItemListener iL) { //Where the GUI is created: JMenuBar menuBar; JMenu menu; JMenuItem menuItem; JCheckBoxMenuItem cbMenuItem; //Create the menu bar. menuBar = new JMenuBar(); //Build the first menu. menu = new JMenu("File"); menu.setMnemonic(KeyEvent.VK_F); menuBar.add(menu); menuItem = new JMenuItem("Quit", KeyEvent.VK_Q); menuItem.setAccelerator(KeyStroke.getKeyStroke( KeyEvent.VK_Q, ActionEvent.CTRL_MASK)); menuItem.addActionListener(aL); menu.add(menuItem); //Build second menu in the menu bar. menu = new JMenu("View"); menu.setMnemonic(KeyEvent.VK_V); menuBar.add(menu); cbMenuItem = new JCheckBoxMenuItem("Show legend"); cbMenuItem.setMnemonic(KeyEvent.VK_L); cbMenuItem.addItemListener(iL); menu.add(cbMenuItem); return menuBar; } }