Friday, March 18, 2016

Count All Lines of Java Code

I posted this to StackOverflow but I think it also belongs here.
    public static void main(String[] args) throws FileNotFoundException {
        countLinesOfCodeInFolder("D:\\Dev\\actio_parser\\java\\Parser\\src");
    }

    private static void countLinesOfCodeInFolder(final String folderPath) throws FileNotFoundException {
        long totalLineCount = 0;
        final List folderList = new LinkedList<>();
        folderList.add(new File(folderPath));
        while (!folderList.isEmpty()) {
            final File folder = folderList.remove(0);
            if (folder.isDirectory() && folder.exists()) {
                System.out.println("Scanning " + folder.getName());
                final File[] fileList = folder.listFiles();
                for (final File file : fileList) {
                    if (file.isDirectory()) {
                        folderList.add(file);
                    } else if (file.getName().endsWith(".java")
                            || file.getName().endsWith(".sql")) {
                        long lineCount = 0;
                        final Scanner scanner = new Scanner(file);
                        while (scanner.hasNextLine()) {
                            scanner.nextLine();
                            lineCount++;
                        }
                        totalLineCount += lineCount;
                        final String lineCountString;
                        if (lineCount > 99999) {
                            lineCountString = "" + lineCount;
                        } else {
                            final String temp = ("     " + lineCount);
                            lineCountString = temp.substring(temp.length() - 5);
                        }
                        System.out.println(lineCountString + " lines in " + file.getName());
                    }
                }
            }
        }
        System.out.println("Scan Complete: " + totalLineCount + " lines total");
    }

Friday, August 21, 2015

WindowFocusListener for a JInternalFrame

UPDATE: [[ If you're curious, refreshing the window on focus did not work well for me. They would double-click on a table in an out-of-focus window, the table would refresh on focus, and the double click event would fire against a different record than the one upon which the original click was made. ]]
 
Here's an example focus listener for a JInternalFrame:
 
 
package com.apexroot.sandbox;

import java.awt.Dimension;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;

/**
 * author grants unlimited license to modify, reuse and redistribute. based on 
 * the suggestion by @mKorbel on stackoverflow at
 * http://stackoverflow.com/questions/7219860/jinternalframe-selection
 * please keep a URL to the original version in the source code.
 * http://javajon.blogspot.com/2015/08/windowfocuslistener-for-jinternalframe.html
 *
 * @author Apexroot
 */
public class InternalFrameFocusListenerExample {

    public static final String INTERNAL_FRAME_FOCUS_EVENT_PROPERTY = "selected";

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {

                final JFrame jFrame = new JFrame();
                final JDesktopPane jDesktopPane = new JDesktopPane();
                final JInternalFrame[] jInternalFrames = new FocusInternalFrame[3];
                for (int i = 0; i < jInternalFrames.length; i++) {
                    jInternalFrames[i] = new FocusInternalFrame();
                }
                jFrame.dispose();
                jFrame.setContentPane(jDesktopPane);
                jDesktopPane.setPreferredSize(new Dimension(400, 200));
                jFrame.pack();
                jFrame.setVisible(true);
                for (int i = 0; i < jInternalFrames.length; i++) {
                    jDesktopPane.add(jInternalFrames[i]);
                    jInternalFrames[i].setLocation(10 + 60 * i, 10 + 40 * i);
                    jInternalFrames[i].setVisible(true);
                }

            }

        });

    }

    public static class FocusInternalFrame extends JInternalFrame {

        public FocusInternalFrame() {

            final JLabel jLabel = new JLabel("placeholder for pack();");
            setContentPane(jLabel);
            pack();

            this.addPropertyChangeListener(
                    INTERNAL_FRAME_FOCUS_EVENT_PROPERTY,
                    new LabelFocusListener(jLabel));

        }

    }

    private static class LabelFocusListener implements PropertyChangeListener {

        private final JLabel jLabel;

        public LabelFocusListener(JLabel jLabel) {
            this.jLabel = jLabel;
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            // please keep a URL to the original version in the source code.
            // http://javajon.blogspot.com/2015/08/windowfocuslistener-for-jinternalframe.html
            if (INTERNAL_FRAME_FOCUS_EVENT_PROPERTY.equals(
                    evt.getPropertyName())) {
                final Object oldValue = evt.getOldValue();
                final Object newValue = evt.getNewValue();
                if (oldValue instanceof Boolean
                        && newValue instanceof Boolean) {
                    boolean wasInFocus = (Boolean) oldValue;
                    boolean isInFocus = (Boolean) newValue;
                    if (isInFocus && !wasInFocus) {
                        EventQueue.invokeLater(new Runnable() {
                            @Override
                            public void run() {

                                // focus gained
                                jLabel.setText("focus gained");
                            }
                        });
                    } else if (wasInFocus && !isInFocus) {
                        EventQueue.invokeLater(new Runnable() {
                            @Override
                            public void run() {

                                // focus lost
                                jLabel.setText("focus lost");
                            }
                        });
                    }
                }
            }
        }
    }
}

Wednesday, July 30, 2014

Java Swing "Flash" a Component

One line of code:
    new ComponentFlasher().flashNow(myTextField);  // works on buttons too

Sometimes people need help finding messages on screen, or seeing data that has changed. The above code will change the color of the text in the text field myTextField to red, then quickly fade it back to black.

 

ComponentFlasher.java :


import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

// Please feel free to reuse, modify and distribute.  
// Please add your own url or email address to this list:
//
// - http://javajon.blogspot.com/
//

/**
 * Class to make it easy to flash a component.
 */
public class ComponentFlasher {

    private final List flashQueue = new LinkedList();
    private final Object FLASH_LOCK = new Object();
    private volatile JComponent flashCurrent = null;
    private final FlashTimer flashTimer = new FlashTimer();
    private final int maxQueueSize = 3;

    public void flashQueue(JComponent componentToAddToQueue) {
        synchronized (FLASH_LOCK) {
            if (flashCurrent == componentToAddToQueue) {
                flashReset(componentToAddToQueue);
            } else {
                final boolean restartTimer = flashQueue.isEmpty() && (flashCurrent == null);
                flashQueue.remove(componentToAddToQueue);
                flashQueue.add(componentToAddToQueue);
                if (restartTimer) {
                    flashTimer.start();
                }
            }
        }
    }

    public void flashNow(final JComponent componentToFlashNow) {
        synchronized (FLASH_LOCK) {
            if (flashCurrent != componentToFlashNow) {
                final boolean restartTimer;
                if (flashCurrent != null) {
                    final JComponent componentToPutBackIntoTheQueue = flashCurrent;
                    flashQueue.remove(componentToPutBackIntoTheQueue);
                    flashQueue.add(0, componentToPutBackIntoTheQueue);

                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            componentToPutBackIntoTheQueue.setForeground(Color.BLACK);
                        }
                    });

                    restartTimer = false;
                } else {
                    restartTimer = flashQueue.isEmpty();
                }
                flashCurrent = componentToFlashNow;
                if (restartTimer) {
                    flashTimer.start();
                }
            }
            flashReset(componentToFlashNow);
        }
    }

    private void flashReset(final JComponent componentToFlashNow) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                componentToFlashNow.setForeground(Color.RED);
            }
        });
    }

    private void flashIteration(final JComponent componentToFlash) {
        // run this code on the UI thread
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                // find out how red we still are
                final int red = flashCurrent.getForeground().getRed() - 10;
                if (red > 0) {
                    componentToFlash.setForeground(new Color(red, 0, 0));
                } else {
                    synchronized (FLASH_LOCK) {
                        if (flashCurrent == componentToFlash) {
                            flashCurrent = null;
                        }
                    }
                }
            }
        });
    }

    private void checkQueue() {
        // no current component - check queue
        if (!flashQueue.isEmpty()) {
            synchronized (FLASH_LOCK) {
                // recheck the same flags we just checked
                // now that we have the lock we are sure they won't change.
                if (flashCurrent == null) {
                    if (flashQueue.isEmpty()) {
                        // stop the timer
                        flashTimer.stop();
                    } else {
                        // make the next item current
                        while (flashQueue.size() > (maxQueueSize + 1)) {
                            flashQueue.remove(0);
                        }
                        final JComponent next = flashQueue.remove(0);
                        flashCurrent = next;
                        flashReset(next);
                    }
                }
            }
        }
    }

    private class FlashTimer extends Timer {

        public FlashTimer() {
            super(50, new FlashTimerAction());
        }
    }

    private class FlashTimerAction implements ActionListener {

        public FlashTimerAction() {
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            // is there a current component ?
            if (flashCurrent == null) {
                checkQueue();
            } else {
                // current component - copy to send to anon-implementation Runnable
                flashIteration(flashCurrent);
            }
        }
    }
}


Wednesday, July 23, 2014

Easy Right-Click Menus for Any Java Swing Application

Add right mouse click (Select All, Copy, Paste) to an entire Java Swing application in one line of code. Sound too good to be true? Before you load your first screen:

new RightMouseMenuMaker().start();

// load your first frame, window, or swingworker here.
 
Add the above line of code and the following file to your project and you're done.
 
RightMouseMenuMaker.java :

import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Container;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction;

/**
 * Library code to add RIGHT-CLICK menus to a Java Swing desktop application.
 * usage:  
new RightMouseMenuMaker().start();
* * @author javajon.blogspot.com */ public class RightMouseMenuMaker implements AWTEventListener, ContainerListener { private boolean enabled = true; /** * register this event listener as a global event listener. */ public void start() { Toolkit.getDefaultToolkit().addAWTEventListener(this, -1L); } /** * * @return the enabled state */ public boolean isEnabled() { return enabled; } /** * enable or disable this event listener. * * @param enabled */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * @see java.awt.event.AWTEventListener * @param event */ @Override public void eventDispatched(AWTEvent event) { if (!this.enabled) { return; } if (event instanceof ContainerEvent) { ContainerEvent ce = (ContainerEvent) event; if (ce.getID() == ContainerEvent.COMPONENT_ADDED) { componentAdded(ce); } } } /** * @see java.awt.event.ContainerListener * @param ce */ @Override public void componentAdded(ContainerEvent ce) { final Component child = ce.getChild(); addRightMouseContextMenus(child); } /** * @see java.awt.event.ContainerListener * @param ce */ @Override public void componentRemoved(ContainerEvent e) { } /** * queue to allow the right mouse menus to be added on a background thread * after the screen has been displayed. on larger screens with numerous * controls, adding the right menu click to each component may take some * time. */ private static final BlockingQueue componentsWaitingForRightMouseMenus = new LinkedBlockingDeque<>(); /** * background thread to add the right mouse menus after the screen has been * displayed. on larger screens with numerous controls, adding the right * menu click to each component may take some time. */ private static final Thread rightMouseMenuMakerThread = new Thread(new ComponentQueue(), "Right Mouse Menu Maker"); /** * start the background thread as soon as the first instance of this class * is created. */ static { rightMouseMenuMakerThread.start(); } /** * adds a right mouse menu to a component and any child components. * @param component */ public static void addRightMouseContextMenus(Component component) { try { componentsWaitingForRightMouseMenus.put(component); } catch (InterruptedException ex) { Logger.getLogger(RightMouseMenuMaker.class.getName()) .log(Level.SEVERE, null, ex); } } /** * adds a right mouse menu to a component and any child components. * @param component */ private static void addRightMouseContextMenusPrivate(Component component) { if (component instanceof JTextComponent) { JTextComponent textComponent = (JTextComponent) component; final JPopupMenu existingContextMenu = textComponent.getComponentPopupMenu(); if (existingContextMenu == null) { final JPopupMenu newContextMenu = getContextMenu(textComponent); textComponent.setComponentPopupMenu(newContextMenu); } } if (component instanceof Container) { Container container = (Container) component; for (Component c : container.getComponents()) { addRightMouseContextMenusPrivate(c); } } } /** * creates a right mouse menu for any JTextComponent. * @return right mouse menu * @see javax.swing.text.JTextComponent * @param textComponent */ public static JPopupMenu getContextMenu( final JTextComponent textComponent) { JMenuItem menuItem; // menu container final JPopupMenu rightClickMenu; rightClickMenu = new JPopupMenu("Edit"); // ----------- final TextAction selectAllAction = new TextAction("Select All") { @Override public void actionPerformed(ActionEvent e) { textComponent.selectAll(); if (getFocusedComponent() != textComponent) { textComponent.grabFocus(); } } }; menuItem = new JMenuItem(selectAllAction); menuItem.setMnemonic(KeyEvent.VK_A); rightClickMenu.add(menuItem); // ----------- rightClickMenu.addSeparator(); // ----------- final TextAction cutAction = new TextAction("Cut") { @Override public void actionPerformed(ActionEvent e) { final String selectedText = textComponent.getSelectedText(); if (selectedText == null || "".equals(selectedText)) { textComponent.selectAll(); } textComponent.cut(); if (getFocusedComponent() != textComponent) { textComponent.grabFocus(); } } }; menuItem = new JMenuItem(cutAction); menuItem.setMnemonic(KeyEvent.VK_T); rightClickMenu.add(menuItem); // ----------- rightClickMenu.addSeparator(); // ----------- final TextAction copyAction = new TextAction("Copy") { @Override public void actionPerformed(ActionEvent e) { final String selectedText = textComponent.getSelectedText(); if (selectedText == null || "".equals(selectedText)) { textComponent.selectAll(); } textComponent.copy(); if (getFocusedComponent() != textComponent) { textComponent.grabFocus(); } } }; menuItem = new JMenuItem(copyAction); menuItem.setMnemonic(KeyEvent.VK_C); rightClickMenu.add(menuItem); // ----------- final TextAction copyAllAction = new TextAction(" Copy All") { @Override public void actionPerformed(ActionEvent e) { textComponent.selectAll(); textComponent.copy(); if (getFocusedComponent() != textComponent) { textComponent.grabFocus(); } } }; menuItem = new JMenuItem(copyAllAction); menuItem.setFont(menuItem.getFont() .deriveFont(menuItem.getFont().getSize2D() * 0.75f)); menuItem.setMnemonic(KeyEvent.VK_O); rightClickMenu.add(menuItem); // ----------- rightClickMenu.addSeparator(); // ----------- final TextAction pasteIntoAction = new TextAction("Paste") { @Override public void actionPerformed(ActionEvent e) { textComponent.paste(); if (getFocusedComponent() != textComponent) { textComponent.grabFocus(); } } }; menuItem = new JMenuItem(pasteIntoAction); menuItem.setMnemonic(KeyEvent.VK_P); rightClickMenu.add(menuItem); // ----------- final TextAction pasteReplaceAllAction = new TextAction(" Replace All") { @Override public void actionPerformed(ActionEvent e) { textComponent.selectAll(); textComponent.paste(); if (getFocusedComponent() != textComponent) { textComponent.grabFocus(); } } }; menuItem = new JMenuItem(pasteReplaceAllAction); menuItem.setFont(menuItem.getFont() .deriveFont(menuItem.getFont().getSize2D() * 0.75f)); menuItem.setMnemonic(KeyEvent.VK_O); rightClickMenu.add(menuItem); // ----------- final TextAction pasteInsertAtStartAction = new TextAction(" Insert at Start") { @Override public void actionPerformed(ActionEvent e) { textComponent.select(0, 0); textComponent.paste(); textComponent.select(0, 0); if (getFocusedComponent() != textComponent) { textComponent.grabFocus(); } } }; menuItem = new JMenuItem(pasteInsertAtStartAction); menuItem.setFont(menuItem.getFont() .deriveFont(menuItem.getFont().getSize2D() * 0.75f)); menuItem.setMnemonic(KeyEvent.VK_B); rightClickMenu.add(menuItem); // ----------- final TextAction pasteAppendAtEndAction = new TextAction(" Append at End") { @Override public void actionPerformed(ActionEvent e) { final String text = textComponent.getText(); if (text == null) { textComponent.select(0, 0); } else { final int length = text.length(); textComponent.select(length, length); } textComponent.paste(); if (getFocusedComponent() != textComponent) { textComponent.grabFocus(); } } }; menuItem = new JMenuItem(pasteAppendAtEndAction); menuItem.setFont(menuItem.getFont() .deriveFont(menuItem.getFont().getSize2D() * 0.75f)); menuItem.setMnemonic(KeyEvent.VK_E); rightClickMenu.add(menuItem); return rightClickMenu; } static class ComponentQueue implements Runnable { public ComponentQueue() { } @Override public void run() { try { while (true) { final ArrayList arrayList = new ArrayList(); // block the thread -- wait until there is a component arrayList.add(componentsWaitingForRightMouseMenus.take()); componentsWaitingForRightMouseMenus.drainTo(arrayList); // short delay -- any more components right away? while (true) { final Component maybe = componentsWaitingForRightMouseMenus .poll(500L, TimeUnit.MILLISECONDS); if (maybe == null) { break; } else { arrayList.add(maybe); componentsWaitingForRightMouseMenus .drainTo(arrayList); } } // marshall the list of components to the UI thread // for further processing SwingUtilities.invokeLater(new Runnable() { @Override public void run() { for (final Component component : arrayList) { addRightMouseContextMenusPrivate(component); } } }); } } catch (InterruptedException ex) { Logger.getLogger(RightMouseMenuMaker.class.getName()) .log(Level.SEVERE, null, ex); } } } }

Friday, July 18, 2014

Java Desktop (e.g. Swing) Network Deployment Batch File

Most networks are fast enough these days to run your JAR right from the network share, but file locking makes deployment difficult with multiple users.

This batch file checks the network location for a newer versions of the JAR file and lib/ folder, and updates the local copy only if a newer copy is available. Then the latest copy is executed locally.

Network-Java-App-Deploy.bat
@echo off

rem JAR file and installation folder (no trailing slashes):
set jar_file=myjavaprogram.jar
set dist_dir=\\server\share\folder

rem users should have read/write access to this folder:
set inst_dir=%appdata%\%jar_file%

rem customize the text to appear in the command window title bar
title Java Application Updater
cls

echo.
echo Java Application Updater
echo.
echo Please feel free to use, reuse, modify, reproduce and distribute.
echo Code is as-is, without warrantee.  User assumes all liability for use.
echo.
echo JAR:   %jar_file%
echo FROM:  %dist_dir%
echo TO:    %inst_dir%
echo.
echo Close the window to cancel . . .
pause

echo.
echo when redistributing, please keep the following URLs 
echo here in the code, so that other's may see the sources
echo and the versions of this batch file:
echo.
echo  - http://javajon.blogspot.com/
echo.

echo.
echo verify %inst_dir%, %username%, temp and lib ...
mkdir "%inst_dir%"
mkdir "%inst_dir%\%username%"
mkdir "%inst_dir%\%username%\temp"
mkdir "%inst_dir%\%username%\lib"

echo.
echo copy application jar file:  %username%\%jar_file% ...
xcopy /D/C/H/R/I/F/K/Y/V "%dist_dir%\%jar_file%" "%inst_dir%\%username%\"

echo.
echo copy lib folder:  %username%\lib ...
xcopy /D/C/H/R/I/F/K/Y/E/V "%dist_dir%\lib\*.*" "%inst_dir%\%username%\lib\"
echo.

cd /D "%inst_dir%\%username%"
cd "%inst_dir%\%username%"
start %jar_file%

goto :skip_xcopy_switches_explanation

 These are the XCOPY command line switches we are using:
 
  single: /D/C/H/R/I/F/K/Y/V
  folder: /D/C/H/R/I/F/K/Y/E/V

 These are the above switches briefly described:
  
  /D - Copy files that are newer than the destination files
  /C - Continue even on error
  /H - Copy hidden and system files, too
  /R - Overwrite read-only files
  /I - Copies more than one file to destination 
    (assumes destination is a directory)
  /F - Displays full path and destination while copying files
  /K - Copies attributes (as opposed to resetting read-only attributes)
  /Y - Copy without confirming replacement of existing files
  /E - Copies any subdirectories, even if they are empty
  /V - Verifies each file as it is written to the destination file

:skip_xcopy_switches_explanation

Add JMenuItem to JMenu after the menu has already been displayed to the user.

How I would do it ...


Temporarily disable new menus to prevent accidental clicks ...


MyMenuListener - How to use it:
        fileMenu.addMenuListener(new MyMenuListener(fileMenu) {

            @Override
            protected void quickMenu(MyMenuListener.MyMenuWorker menuWorker) {

                menuWorker.add(new JMenuItem("Fast New Menu A"));
                menuWorker.add(new JMenuItem("Fast New Menu B"));

            }

            @Override
            protected void slowMenu(MyMenuListener.MyMenuWorker menuWorker) {

                /** do something here that takes a long time. */
                
                menuWorker.add(new JMenuItem("Delayed Menu 1"));
                menuWorker.add(new JMenuItem("Delayed Menu 2"));
                
            }
        });



MyMenuListener - The code:  (as a single executable java file)
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;

/**
 * Demonstrates how to add menu items to a menu, dynamically on a background
 * thread using a SwingWorker, after the menu has already been displayed on
 * screen.
 *
 * @author javajon.blogspot.com
 */
public class SwingWorkerMenuBar extends javax.swing.JFrame {

    private javax.swing.JMenu fileMenu;

    /**
     * Creates new form SwingWorkerMenuBar. (yay?)
     */
    public SwingWorkerMenuBar() {
        initComponents();

        /**
         * this part is required for the menu to be dynamic.
         */
        fileMenu.addMenuListener(new MyMenuListener(fileMenu) {

            /**
             * This is the part that catches the menu just before it's displayed
             * so we can make immediate modifications. Any modifications you can
             * make from memory (i.e. without hitting a database or a file
             * system) can be made here and will display with the menu the first
             * time it becomes visible. Caution: if you put your
             * long-running code here then the menu won't show up at all until
             * your long-running code is done.
             */
            @Override
            protected void quickMenu(MyMenuListener.MyMenuWorker menuWorker) {

                menuWorker.add(new JMenuItem("Fast New Menu A"));
                menuWorker.add(new JMenuItem("Fast New Menu B"));

            }

            /**
             * this is where we add menus that might take some time to build ie
             * data has to be looked up in the database or read from a file
             * store or network share.
             */
            @Override
            protected void slowMenu(MyMenuListener.MyMenuWorker menuWorker) {
                for (int i = 500; i >= 50; i = (i / 33) * 25) {
                    try {

                        // simulate processing & send a new menu to UI
                        Thread.sleep(i);
                        menuWorker.add(new JMenuItem("New Menu " + i + " Delay"));

                    } catch (InterruptedException ex) {
                        Logger.getLogger(SwingWorkerMenuBar.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        });
        
    }

    public static abstract class MyMenuListener implements MenuListener, ActionListener {

        /**
         * This is the part that catches the menu just before it's displayed so
         * we can make immediate modifications. Any modifications you can make
         * from memory (i.e. without hitting a database or a file system) can be
         * made here and will display with the menu the first time it becomes
         * visible. Caution: if you put your long-running code here then
         * the menu won't show up at all until your long-running code is done.
         *
         * @param menuWorker use this object to add new menus
         */
        protected abstract void quickMenu(MyMenuWorker menuWorker);

        /**
         * this is where we add menus that might take some time to build ie data
         * has to be looked up in the database or read from a file store or
         * network share.
         *
         * @param menuWorker use this object to add new menus
         */
        protected abstract void slowMenu(MyMenuWorker menuWorker);

        /**
         * the menu with which this listener is associated.
         */
        private final JMenu menu;

        /**
         * the menus that have been created using menuWorker.add(menuItem).
         */
        private final List myMenus = new ArrayList();

        /**
         * the menus that have been disabled to prevent the user from
         * accidentally clicking on a menu that wasn't there a moment ago.
         */
        private final List menusToEnable = new ArrayList();

        /**
         * basic lock object to make sure threads are playing nice with shared
         * memory.
         */
        private final Object LOCK = new Object();

        /**
         * this is the part that re-enables menu options.
         */
        private final javax.swing.Timer timer = new Timer(300, this);

        /**
         * constructor.
         * @param menu 
         */
        public MyMenuListener(final JMenu menu) {
            this.menu = menu;
        }

        /**
         * this happens when the user clicks the menu, but before the pop up
         * menu appears.
         * @param e 
         */
        @Override
        public void menuSelected(MenuEvent e) {
            new MyMenuWorker(menu).execute();
        }

        @Override
        public void menuDeselected(MenuEvent e) {

        }

        @Override
        public void menuCanceled(MenuEvent e) {

        }

        /**
         * background worker that can do long-running tasks such as database
         * queries and then add more menu options to the list after the list has
         * already been displayed.
         */
        public class MyMenuWorker extends SwingWorker {

            private boolean quick;
            private int insertAt = 0;
            private final JMenu menu;

            public MyMenuWorker(JMenu menu) {
                synchronized (LOCK) {
                    this.menu = menu;                    

                    // clear out menus that were added last time
                    for (int i = 0; i < menu.getItemCount(); i++) {
                        final JMenuItem menuItem = menu.getItem(i);
                        if (menuItem != null && myMenus.contains(menuItem)) {
                            if (myMenus.remove(menuItem)) {
                                menu.remove(menuItem);
                                i--;
                            }
                        }
                    }

                    // add instant menus
                    this.quick = true;
                }
                quickMenu(this);
                synchronized (LOCK) {
                    this.insertAt = menu.getItemCount(); // end of list
                }
            }

            @Override
            protected Boolean doInBackground() throws Exception {
                synchronized (LOCK) {
                    this.quick = false;
                }
                slowMenu(this);
                return null;
            }

            /**
             * add menu items after the menu has already been shown. these are
             * the menu items that are published above using publish().
             *
             * @param menuItems menu items to add
             */
            @Override
            protected void process(List menuItems) {
                final int originalInsert = insertAt;
                for (final JMenuItem item : menuItems) {
                    myMenus.add(item);
                    menu.add(item, insertAt++);
                }
                tempDisable(menu, originalInsert);
                resizeMenu();
            }

            /**
             * feel free to reuse, modify, or distribute.
             * http://javajon.blogspot.com/
             */
            private void resizeMenu() {
                final JPopupMenu popupMenu = menu.getPopupMenu();
                if (popupMenu.isVisible()) {

                    // redraw the menu
                    menu.revalidate();

                    // resize the menu
                    popupMenu.setVisible(false);
                    popupMenu.setVisible(true);

                }
            }

            /**
             * report errors.
             */
            @Override
            protected void done() {
                try {
                    get();
                } catch (InterruptedException ex) {
                    Logger.getLogger(SwingWorkerMenuBar.class.getName()).log(Level.INFO, null, ex);
                } catch (ExecutionException ex) {
                    Logger.getLogger(SwingWorkerMenuBar.class.getName()).log(Level.INFO, null, ex);
                }
            }

            /**
             * when called from quickMenu() this method
             *
             * @param item
             */
            void add(JMenuItem item) {
                if (quick) {
                    myMenus.add(menu.add(item));
                } else {
                    publish(item);
                }
            }

        }

        /**
         * temporarily disables menu options because they are changing and you
         * don't want the user to click and have the menu option change just
         * before the click is made.
         *
         * @param menu
         * @param firstIndex
         */
        private void tempDisable(
                final JMenu menu,
                final int firstIndex) {

            synchronized (LOCK) {
                int insertAt = 0;
                for (int i = firstIndex; i < menu.getItemCount(); i++) {
                    final JMenuItem menuItem = menu.getItem(i);
                    if (menuItem.isEnabled()) {
                        menuItem.setEnabled(false);
                        if (!menusToEnable.contains(menuItem)) {
                            menusToEnable.add(insertAt++, menuItem);
                        }
                    }
                }

                timer.start();
            }

        }

        /**
         * this is the code that re-enables menus when they have been disabled
         * to prevent accidental clicking. this is the ActionListener
         * implementation for the timer task.
         *
         * @param e
         */
        @Override
        public void actionPerformed(ActionEvent e) {

            synchronized (LOCK) {
                if (menusToEnable.isEmpty()) {

                    /**
                     * if there's nothing to do, kill the timer.
                     */
                    timer.stop();

                } else {

                    /**
                     * re-enable the menus.
                     */
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            synchronized (LOCK) {
                                if (!menusToEnable.isEmpty()) {
                                    // enable the first menu that's disabled
                                    for (int i = 0; i < menu.getItemCount(); i++) {
                                        final JMenuItem enableMe = menu.getItem(i);
                                        if (menusToEnable.contains(enableMe)) {
                                            // enable one custom menu and quit
                                            enableMe.setEnabled(true);
                                            enableMe.revalidate();
                                            menusToEnable.remove(enableMe);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    });

                } // empty / kill timer
            } // synchronized
        }

    }

    /**
     * create a simple form with a file menu.
     */
    private void initComponents() {
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        /**
         * simulate a file menu with a single menu option on it.
         */
        final javax.swing.JMenuBar menuBar;
        setJMenuBar(menuBar = new javax.swing.JMenuBar());
        menuBar.add(this.fileMenu = new javax.swing.JMenu("File"));
        fileMenu.add("Existing Menu I");
        fileMenu.add("Existing Menu II");

        /**
         * form content, form size (i think), and center on screen.
         */
        getContentPane().setPreferredSize(new Dimension(400, 260));
        pack();
        setLocationRelativeTo(null);

    }

    /**
     * default program entry point -- generated by NetBeans.
     *
     * @param args the command line arguments
     */
    public static void main(String args[]) {

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new SwingWorkerMenuBar().setVisible(true);
            }
        });
    }

}






Tuesday, July 23, 2013

Java AWT/Swing getComponentVariableName(component)

Sometimes you have a component that you know almost nothing about, and it'd be nice to get the name of it, somehow, but since I haven't taken the time to explicitly name every one of my components, I tried something else. Here's how to use it ...


    private void genericErrorHandler(java.awt.event.AWTEvent evt) {

        String componentName = Awt2.getComponentVariableName(evt.getSource());
        System.out.println(componentName);

    }



Here's the code to make the above code work...


import java.awt.Component;
import java.lang.reflect.Field;

/**
 * additional utilities for working with AWT/Swing.
 * this is a single method for demo purposes.
 * recommended to be combined into a single class
 * module with other similar methods,
 * e.g. MySwingUtilities
 *
 * @author http://javajon.blogspot.com/2013/07/java-awtswing-getcomponentvariablenamec.html
 */
public class Awt2 {

    /**
     * substitute for component.getName() when used in NetBeans or other IDE
     * that creates class fields to hold the components. uses reflection to
     * search through class fields for a match.
     * @param component the component to look for
     * @return hopefully the variable name used to hold this component
     */
    static public String getComponentVariableName(Object object) {

        if (object instanceof Component) {
            final Component component = (Component) object;
            final StringBuilder sb = new StringBuilder();

            // find the form where the variable name would be likely to exist
            final Component parentForm = getParentForm(component);

            // loop through all of the class fields on that form
            for (Field field : parentForm.getClass().getDeclaredFields()) {

                try {
                    // let us look at private fields, please
                    field.setAccessible(true);

                    // get a potential match
                    final Object potentialMatch = field.get(parentForm);

                    // compare it
                    if (potentialMatch == component) {

                        // return the name of the variable used
                        // to hold this component
                        if (sb.length() > 0) sb.append(",");
                        sb.append(field.getName());
                    }

                } catch (SecurityException | IllegalArgumentException 
                        | IllegalAccessException ex) {

                    // ignore exceptions
                }
            }

            if (sb.length() > 0) {
                return sb.toString();
            }
        }

        // if we get here, we're probably trying to find the form
        // itself, in which case it may be more useful to print
        // the class name (MyJFrame) than the AWT-assigned name
        // of the form (frame0)
        final String className = object.getClass().getName();
        final String[] split = className.split("\\.");
        final int lastIndex = split.length - 1;
        return (lastIndex >= 0) ? split[lastIndex] : className;

    }

    /**
     * traverses up the component tree to find the top, which i assume is the
     * dialog or frame upon which this component lives.
     * @param sourceComponent
     * @return top level parent component
     */
    static public Component getParentForm(Component sourceComponent) {
        while (sourceComponent.getParent() != null) {
            sourceComponent = sourceComponent.getParent();
        }
        return sourceComponent;
    }
}



Using the above code, here's an example of a global AWT/Swing event catcher that just dumps everything to the system console...


    Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {

        @Override
        public void eventDispatched(AWTEvent event) {
            System.out.print(Awt2.getComponentVariableName(event.getSource()));
            System.out.print(": ");
            System.out.print(event.paramString());
            System.out.println();
        }
    }, -1L);