OOMetrics.java

/*
 *  Copyright (c) 2001-2024, Jean Tessier
 *  All rights reserved.
 *  
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *  
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *  
 *      * Neither the name of Jean Tessier nor the names of his contributors
 *        may be used to endorse or promote products derived from this software
 *        without specific prior written permission.
 *  
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
 *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jeantessier.dependencyfinder.gui;

import com.jeantessier.commandline.CommandLine;
import com.jeantessier.commandline.CommandLineUsage;
import com.jeantessier.commandline.NullParameterStrategy;
import com.jeantessier.metrics.MetricsConfigurationLoader;
import com.jeantessier.metrics.MetricsFactory;
import org.apache.logging.log4j.*;

import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.table.TableCellRenderer;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.io.File;

public class OOMetrics extends JFrame {
    private static final TableCellRenderer RENDERER = new MeasurementTableCellRenderer();

    private MetricsFactory factory;

    private final JMenuBar menuBar = new JMenuBar();
    private final JMenu fileMenu = new JMenu();
    private final JMenu helpMenu = new JMenu();
    private final JToolBar toolbar = new JToolBar();
    private final JTextArea projectArea = new JTextArea();
    private final JButton filterButton = new JButton("Filter:");
    private final JTextField filterField = new JTextField("//");
    private final StatusLine statusLine = new StatusLine(420);
    private final JProgressBar progressBar = new JProgressBar();

    private final OOMetricsTableModel groupsModel;
    private final OOMetricsTableModel classesModel;
    private final OOMetricsTableModel methodsModel;

    private File inputFile = new File(".");

    private final boolean enableCrossClassMeasurements;

    public OOMetrics(MetricsFactory factory, boolean enableCrossClassMeasurements) {
        this.factory = factory;
        this.enableCrossClassMeasurements = enableCrossClassMeasurements;

        this.setSize(new Dimension(800, 600));
        this.setTitle("OO Metrics");
        this.setIconImage(new ImageIcon(getClass().getResource("icons/logoicon.gif")).getImage());
        this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        this.addWindowListener(new WindowKiller());

        groupsModel = new OOMetricsTableModel(factory.getConfiguration().getGroupMeasurements());
        classesModel = new OOMetricsTableModel(factory.getConfiguration().getClassMeasurements());
        methodsModel = new OOMetricsTableModel(factory.getConfiguration().getMethodMeasurements());

        buildMenus();
        buildUI();

        try {
            UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
            SwingUtilities.updateComponentTreeUI(this);
        } catch (Exception ex) {
            LogManager.getLogger(OOMetrics.class).error("Unable to set look and feel", ex);
        }

        statusLine.showInfo("Ready.");
    }

    MetricsFactory getMetricsFactory() {
        return factory;
    }

    void setMetricsFactory(MetricsFactory factory) {
        this.factory = factory;
    }

    JTextArea getProjectArea() {
        return projectArea;
    }

    OOMetricsTableModel getGroupsModel() {
        return groupsModel;
    }

    OOMetricsTableModel getClassesModel() {
        return classesModel;
    }

    OOMetricsTableModel getMethodsModel() {
        return methodsModel;
    }

    File getInputFile() {
        return inputFile;
    }

    void setInputFile(File inputFile) {
        this.inputFile = inputFile;
    }

    JTextComponent getFilterField() {
        return filterField;
    }

    StatusLine getStatusLine() {
        return statusLine;
    }

    JProgressBar getProgressBar() {
        return progressBar;
    }

    public boolean isEnableCrossClassMeasurements() {
        return enableCrossClassMeasurements;
    }

    private void buildMenus() {
        buildFileMenu();
        buildHelpMenu();

        this.setJMenuBar(menuBar);
    }

    private void buildFileMenu() {
        menuBar.add(fileMenu);

        fileMenu.setText("File");

        Action action;
        JMenuItem menuItem;
        JButton button;

        action = new MetricsExtractAction(this);
        menuItem = fileMenu.add(action);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, Event.CTRL_MASK));
        menuItem.setMnemonic('e');
        button = toolbar.add(action);
        button.setToolTipText((String) action.getValue(Action.LONG_DESCRIPTION));

        toolbar.addSeparator();
        fileMenu.addSeparator();

        action = new NewMetricsAction(this);
        menuItem = fileMenu.add(action);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK));
        menuItem.setMnemonic('n');
        button = toolbar.add(action);
        button.setToolTipText((String) action.getValue(Action.LONG_DESCRIPTION));

        toolbar.addSeparator();
        fileMenu.addSeparator();

        action = new ExitAction(this);
        menuItem = fileMenu.add(action);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, Event.CTRL_MASK));
        menuItem.setMnemonic('x');

        this.setJMenuBar(menuBar);
    }

    private void buildHelpMenu() {
        menuBar.add(helpMenu);

        helpMenu.setText("Help");

        Action action;
        JMenuItem menuItem;

        action = new AboutAction(this);
        menuItem = helpMenu.add(action);
        menuItem.setMnemonic('a');
    }
    
    private void buildUI() {
        this.getContentPane().setLayout(new BorderLayout());
        this.getContentPane().add(buildControlPanel(), BorderLayout.NORTH);
        this.getContentPane().add(buildResultPanel(), BorderLayout.CENTER);
        this.getContentPane().add(buildStatusPanel(), BorderLayout.SOUTH);
    }

    private JComponent buildControlPanel() {
        return toolbar;
    }
    
    private JComponent buildResultPanel() {
        JPanel result = new JPanel();

        result.setLayout(new BorderLayout());
        result.add(new JSplitPane(JSplitPane.VERTICAL_SPLIT, buildProjectPanel(), buildChartsPanel()), BorderLayout.CENTER);
        result.add(buildFilterPanel(), BorderLayout.SOUTH);

        return result;
    }

    private JComponent buildProjectPanel() {
        JComponent result = new JScrollPane(projectArea);

        projectArea.setEditable(false);

        return result;
    }

    private JComponent buildChartsPanel() {
        JTabbedPane result = new JTabbedPane();

        // result.setBorder(BorderFactory.createTitledBorder("Data"));
        result.addTab("Groups",  buildGroupsChartPanel());
        result.addTab("Classes", buildClassesChartPanel());
        result.addTab("Methods", buildMethodsChartPanel());

        return result;
    }

    private JComponent buildGroupsChartPanel() {
        return buildChartPanel(getGroupsModel());
    }

    private JComponent buildClassesChartPanel() {
        return buildChartPanel(getClassesModel());
    }

    private JComponent buildMethodsChartPanel() {
        return buildChartPanel(getMethodsModel());
    }

    private JComponent buildChartPanel(OOMetricsTableModel model) {
        JComponent result;

        JTable table = new JTable(model);

        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        table.setRowSelectionAllowed(true);
        table.setDefaultRenderer(Object.class, RENDERER);
        table.setShowHorizontalLines(false);
        table.setShowVerticalLines(false);
        TableHeaderListener listener = new TableHeaderListener(table, model);
        table.getTableHeader().addMouseListener(listener);
        table.getTableHeader().addMouseMotionListener(listener);

        result = new JScrollPane(table);

        return result;
    }

    private JComponent buildFilterPanel() {
        JPanel result = new JPanel();

        result.setLayout(new BorderLayout());
        result.add(filterButton, BorderLayout.WEST);
        result.add(filterField,  BorderLayout.CENTER);

        filterButton.addActionListener(new FilterActionListener(this));

        return result;
    }

    private JComponent buildStatusPanel() {
        JPanel result = new JPanel();

        Dimension size = getProgressBar().getPreferredSize();
        size.width = 100;
        getProgressBar().setPreferredSize(size);
        getProgressBar().setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));

        result.setLayout(new BorderLayout());
        result.add(getStatusLine(),  BorderLayout.CENTER);
        result.add(getProgressBar(), BorderLayout.EAST);

        return result;
    }

    public static void showError(CommandLineUsage clu, String msg) {
        System.err.println(msg);
        showError(clu);
    }

    public static void showError(CommandLineUsage clu) {
        System.err.println(clu);
    }

    public static void main(String[] args) throws Exception {
        // Parsing the command line
        CommandLine commandLine = new CommandLine(new NullParameterStrategy());
        commandLine.addSingleValueSwitch("default-configuration", true);
        commandLine.addSingleValueSwitch("configuration");
        commandLine.addToggleSwitch("validate");
        commandLine.addToggleSwitch("enable-cross-class-measurements");
        commandLine.addToggleSwitch("help");

        CommandLineUsage usage = new CommandLineUsage("OOMetrics");
        commandLine.accept(usage);

        try {
            commandLine.parse(args);
        } catch (IllegalArgumentException ex) {
            showError(usage, ex.toString());
            System.exit(1);
        }

        if (commandLine.getToggleSwitch("help")) {
            showError(usage);
            System.exit(1);
        }

        MetricsFactory factory;

        if (commandLine.isPresent("configuration")) {
            factory = new MetricsFactory("Project", new MetricsConfigurationLoader(commandLine.getToggleSwitch("validate")).load(commandLine.getSingleSwitch("configuration")));
        } else {
            factory = new MetricsFactory("Project", new MetricsConfigurationLoader(commandLine.getToggleSwitch("validate")).load(commandLine.getSingleSwitch("default-configuration")));
        }

        /*
         *  Beginning of main processing
         */

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception ex) {
            // Ignore
        }

        OOMetrics model = new OOMetrics(factory, commandLine.isPresent("enable-cross-class-measurements"));
        model.setVisible(true);
    }
}