/* * [ComparatorCutter.java] * * Summary: Applet to Generate Comparator Java code for sorting. * * Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2009-04-22 work started * 1.1 2009-04-28 initial public release * 1.2 2009-04-28 move to JDK 1.5 from 1.6 * 1.3 2009-05-21 refactor to use enum, and permit generics and nested vs top-level Comparators * 1.4 2009-05-22 many small polishings based on experience converting existing Comparators to use the cutter. * 1.5 2010-01-02 improve prompts, comments, length, add sort order types to sort key summary. * 1.6 2011-09-02 change comments to let know can handle enums. Reference to online Applet in generated code. * 1.7 2011-11-30 nullSafe, @NotNull, configurable L&F, tie breaker for case-insensitive compares. * 1.8 2012-05-26 can now specify target Java version for generated code. * 1.9 2013-01-18 use Misc.compare rather than simple subtraction to compare to handle corner cases correctly. * 2.0 2014-08-01 add JDK 1.8 support. Add enum support. * 2.1 2016-07-04 default now 1.8, two new key types. */ package com.mindprod.comparatorcutter; import com.mindprod.common18.Build; import com.mindprod.common18.CMPAboutJBox; import com.mindprod.common18.FontFactory; import com.mindprod.common18.HybridJ; import com.mindprod.common18.JEButton; import com.mindprod.common18.JavaVersion; import com.mindprod.common18.Laf; import com.mindprod.common18.VersionCheck; import com.mindprod.fastcat.FastCat; import javax.swing.AbstractButton; import javax.swing.ImageIcon; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JEditorPane; 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.JTextArea; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.text.html.HTMLDocument; import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Font; 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.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import static com.mindprod.comparatorcutter.InterfaceType.COMPARATOR_NESTED; import static com.mindprod.comparatorcutter.InterfaceType.COMPARATOR_TOP_LEVEL; import static java.lang.System.*; /** * Applet to Generate Comparator Java code for sorting. * * @author Roedy Green, Canadian Mind Products * @version 2.1 2016-07-04 default now 1.8, two new key types. * @since 2009-04-22 */ public class ComparatorCutter extends JApplet { // declarations /** * version */ static final String VERSION_STRING = "2.1"; /** * true if you want additional debugging output. */ private static final boolean DEBUGGING = false; /** * height of Applet box in pixels. Does not include surrounding frame. */ private static final int APPLET_HEIGHT = 940; /** * Width of Applet box in pixels. */ private static final int APPLET_WIDTH = 582; private static final int FIRST_COPYRIGHT_YEAR = 2009; /** * maximum number of fields we can sort on */ private static final int MAX_FIELDS = 5; /** * undisplayed copyright notice */ @SuppressWarnings( { "UnusedDeclaration" } ) private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; /** * date of last official release */ private static final String RELEASE_DATE = "2016-07-04"; /** * title of Applet */ private static final String TITLE_STRING = "Comparator Cutter"; /** * Applet background a light pink */ private static final Color BACKGROUND_FOR_APPLET = new Color( 0xfff0f0 ); /** * background where for data entry where user enters a value. */ private static final Color BACKGROUND_FOR_EDITABLE = Color.WHITE; /** * instructions background colour */ private static final Color BACKGROUND_FOR_INSTRUCTIONS = new Color( 0xffe8e8 ); /** * field panel background colour */ private static final Color BACKGROUND_FOR_PANEL = new Color( 0xffe8e8 ); /** * data entry text field colour */ private static final Color FOREGROUND_FOR_ENTER = Color.BLACK; /** * instructions text colour */ private static final Color FOREGROUND_FOR_INSTRUCTIONS = new Color( 0x008000 ); /** * label text colour */ private static final Color FOREGROUND_FOR_LABEL = new Color( 0x0000b0 ); /** * results text colour */ private static final Color FOREGROUND_FOR_RESULT = Color.BLACK; /** * for titles */ private static final Color FOREGROUND_FOR_TITLE = new Color( 0xdc143c ); /** * instructions font */ private static final Font FONT_FOR_INSTRUCTIONS = FontFactory.build( "Dialog", Font.PLAIN, 12 ); /** * font for labels */ private static final Font FONT_FOR_LABELS = FontFactory.build( "Dialog", Font.BOLD, 15 ); /** * font for labels */ private static final Font FONT_FOR_LABELS_SMALL = FontFactory.build( "Dialog", Font.BOLD, 12 ); /** * for for titles and About buttons */ private static final Font FONT_FOR_TITLE = FontFactory.build( "Dialog", Font.BOLD, 16 ); /** * for for titles and About buttons */ private static final Font FONT_FOR_TITLE2 = FontFactory.build( "Dialog", Font.PLAIN, 14 ); /** * icon to indicate a field will be sorted in ascending order. */ private static final ImageIcon ASCENDING_ICON = new ImageIcon( ComparatorCutter.class.getResource( "image/ascending.png" ) ); /** * icon to indicate a field will be sorted in descending order. */ private static final ImageIcon DESCENDING_ICON = new ImageIcon( ComparatorCutter.class.getResource( "image/descending.png" ) ); /** * disabled icon for button to request a field be moved down in priority. */ private static final ImageIcon MOVE_DOWN_DISABLED_ICON = new ImageIcon( ComparatorCutter.class.getResource( "image/movedowndisabled.png" ) ); /** * icon for button to request a field be moved down in priority. */ private static final ImageIcon MOVE_DOWN_ICON = new ImageIcon( ComparatorCutter.class.getResource( "image/movedown.png" ) ); /** * disabled icon for button to request a field be moved up in priority. */ private static final ImageIcon MOVE_UP_DISABLED_ICON = new ImageIcon( ComparatorCutter.class.getResource( "image/moveupdisabled.png" ) ); /** * icon for button to request a field be moved up in priority. */ private static final ImageIcon MOVE_UP_ICON = new ImageIcon( ComparatorCutter.class.getResource( "image/moveup.png" ) ); /** * true = ignore events to prevent fibrillation, handlers creating more events in a fusion reaction. */ private boolean defibrillate; /** * Move this field down to lower priority */ private JButton[] fieldMoveDowns; /** * Move this field up to higher priority */ private JButton[] fieldMoveUps; /** * ascending/descending of the various collating fields. Rigid convention descending = true, ascending false. */ private JCheckBox[] fieldIsDescendings; /** * radio button to select null safety */ private JCheckBox useNullSafeBox; /** * types of the various collating fields */ private JComboBox[] fieldTypes; /** * select Comparable/Comparator */ private JComboBox interfaceSelection; /** * radio button to select java version */ private JComboBox javaVersionBox; /** * button to trigger code generation. */ private JEButton generateButton; /** * instructions on how to use the program */ private JEditorPane instructions; /** * label for comment */ private JLabel commentLabel; /** * label for ascending/descending direction */ private JLabel fieldAscDescLabel; /** * label for move down buttons */ private JLabel fieldMoveDownLabel; /** * top line label for the two move buttons */ private JLabel fieldMoveLabel; /** * label for move up buttons */ private JLabel fieldMoveUpLabel; /** * label for field name buttons */ private JLabel fieldNameLabel; /** * used to number the sort key fields */ private JLabel[] fieldNumbers; /** * label for ascending/descending direction */ private JLabel fieldSortLabel; /** * label for type of sort key field */ private JLabel fieldTypeLabel; /** * Label for name of the Comparator/Comparable class we are generating. */ private JLabel gClassLabel; /** * Label for name for the Comparator/Comparable radio button pair */ private JLabel generateLabel; /** * label for instructions */ private JLabel instructionsLabel; /** * label for type of sort key field */ private JLabel javaVersionLabel; /** * Label for name of the class of object we are comparing */ private JLabel oClassLabel; /** * label for generated results */ private JLabel resultsLabel; /** * title */ private JLabel title; /** * title, second line */ private JLabel title2; /** * panel to hold line of fields about each sorting key */ private JPanel fieldPanel; /** * allow user to scroll through instructions */ private JScrollPane instructionsScrollPane; /** * lets user scroll through results. */ private JScrollPane resultsScrollPane; /** * generated code to do the conversion */ private JTextArea results; /** * comment on what comparator/comparable does */ private JTextField comment; /** * names of the various collating fields */ private JTextField[] fieldNames; /** * name of the Comparator/Comparable class we are generating */ private JTextField gClass; /** * name of the class of object we are comparing */ private JTextField oClass; // /declarations // methods /** * Debugging tool. Get name of a component in an Event * * @param e Event * * @return name of the Component */ private static String getName( AWTEvent e ) { try { final String result = ( ( Component ) e.getSource() ).getName(); if ( result == null || result.length() == 0 ) { return "unknown" + e.getSource().getClass().toString() + " Component"; } else { return result; } } catch ( ClassCastException ex ) { return "unknown " + e.getSource().getClass().toString() + " source"; } }// /method /** * record subscript in individual component so we know which row triggered an event. * * @param jButton AbstractButton in an array * @param i index where component lives */ private static void recordFieldRowIndex( AbstractButton jButton, int i ) { jButton.setActionCommand( String.valueOf( ( char ) i ) ); }// /method /** * retrieve subscript of individual Component so we know which row triggered an event. * * @param jButton AbstractButton in an array * * @return i index where component lives */ private static int retrieveFieldRowIndex( AbstractButton jButton ) { return jButton.getActionCommand().charAt( 0 ); }// /method /** * build a menu with Look & Feel and About across the top */ private void buildMenu() { // turn on anti-alias System.setProperty( "swing.aatext", "true" ); final JMenuBar menubar = new JMenuBar(); setJMenuBar( menubar ); final JMenu lafMenu = Laf.buildLookAndFeelMenu(); if ( lafMenu != null ) { menubar.add( lafMenu ); } final JMenu menuHelp = new JMenu( "Help" ); menubar.add( menuHelp ); final JMenuItem aboutItem = new JMenuItem( "About" ); menuHelp.add( aboutItem ); aboutItem.addActionListener( e -> { // open aboutbox frame new CMPAboutJBox( TITLE_STRING, VERSION_STRING, "Generates Java source for Comparators or Comparables,", "for sorting ascending or descending, multiple fields.", "freeware", RELEASE_DATE, FIRST_COPYRIGHT_YEAR, "Roedy Green", "COMPARATORCUTTER", "1.8" ); } ); }// /method /** * compose Comparator or Comparable Java source code based on current settings. */ private void compose() { if ( defibrillate ) { return; } defibrillate = true; tidyFields(); final SortKey[] sortKeys = extractUsedSortKeys(); String tidiedGClass = gClass.getText().trim(); if ( tidiedGClass.length() == 0 ) { tidiedGClass = "InMyOrder"; } String tidiedOClass = oClass.getText().trim(); if ( tidiedOClass.length() == 0 ) { tidiedOClass = "Thing"; } String tidiedComment = comment.getText().trim(); if ( tidiedComment.length() == 0 ) { tidiedComment = "To come"; } final InterfaceType interfaceType = ( InterfaceType ) interfaceSelection.getSelectedItem(); JavaVersion javaVersion = ( JavaVersion ) javaVersionBox.getSelectedItem(); boolean checkForNull = useNullSafeBox.isSelected(); results.setText( interfaceType.compose( javaVersion, checkForNull, tidiedGClass, tidiedOClass, tidiedComment, sortKeys ) ); // Make sure the method header is visible. // invoke later to give time for text to render. SwingUtilities.invokeLater( new Runnable() { public void run() { resultsScrollPane.getVerticalScrollBar().setValue( interfaceType.scrollTo() ); } } ); validate(); defibrillate = false; }// /method /** * create and initialise all the components */ private void createComponents() { title = new JLabel( TITLE_STRING + " " + VERSION_STRING ); title.setName( "title" ); title.setFont( FONT_FOR_TITLE ); title.setForeground( FOREGROUND_FOR_TITLE ); title2 = new JLabel( "released:" + RELEASE_DATE + " build:" + Build.BUILD_NUMBER ); title2.setFont( FONT_FOR_TITLE2 ); title2.setForeground( FOREGROUND_FOR_TITLE ); // not to be confused with Generate button generateLabel = new JLabel( "Generate", JLabel.RIGHT ); generateLabel.setFont( FONT_FOR_LABELS ); generateLabel.setForeground( FOREGROUND_FOR_LABEL ); interfaceSelection = new JComboBox<>( InterfaceType.values() ); interfaceSelection.setName( "interfaceSelection" ); // leave colours the default for best results. interfaceSelection.setToolTipText( "Generate a Comparator to be nested in another class" ); javaVersionBox = new JComboBox<>( JavaVersion.values() ); javaVersionBox.setMaximumRowCount( JavaVersion.values().length ); // turn off the write-in feature javaVersionBox.setEditable( false ); javaVersionBox.setSelectedItem( JavaVersion.J18 ); // no multiple selections permitted. javaVersionBox.setName( "javaVersionBox" ); javaVersionBox.setForeground( FOREGROUND_FOR_LABEL ); javaVersionBox.setBackground( BACKGROUND_FOR_APPLET ); javaVersionBox.setToolTipText( "Which version of Java to generate code for." ); javaVersionLabel = new JLabel( "Java version", JLabel.LEFT ); javaVersionLabel.setFont( FONT_FOR_LABELS ); javaVersionLabel.setForeground( FOREGROUND_FOR_LABEL ); useNullSafeBox = new JCheckBox( "Null Safe", false /* not checked */ ); useNullSafeBox.setName( "useNullSafe" ); useNullSafeBox.setForeground( FOREGROUND_FOR_LABEL ); useNullSafeBox.setBackground( BACKGROUND_FOR_APPLET ); useNullSafeBox.setToolTipText( "add code to deal with null sort key fields" ); oClassLabel = new JLabel( "Object class", JLabel.RIGHT ); oClassLabel.setFont( FONT_FOR_LABELS ); oClassLabel.setForeground( FOREGROUND_FOR_LABEL ); oClass = new JTextField( "Thing" ); oClass.setName( "oClass" ); oClass.setEditable( true ); oClass.setFont( FONT_FOR_LABELS ); oClass.setForeground( FOREGROUND_FOR_ENTER ); oClass.setBackground( BACKGROUND_FOR_EDITABLE ); oClass.setHorizontalAlignment( JTextField.LEFT ); oClass.setToolTipText( "class name of Objects or enums being compared" ); commentLabel = new JLabel( "Comment", JLabel.RIGHT ); commentLabel.setFont( FONT_FOR_LABELS ); commentLabel.setForeground( FOREGROUND_FOR_LABEL ); comment = new JTextField( "Describe/summarise the comparison here.", 30 ); comment.setName( "comment" ); comment.setEditable( true ); comment.setFont( FONT_FOR_LABELS ); comment.setForeground( FOREGROUND_FOR_ENTER ); comment.setBackground( BACKGROUND_FOR_EDITABLE ); comment.setHorizontalAlignment( JTextField.LEFT ); comment.setToolTipText( "Describe/summarise the comparison here, e.g. 'Sorts Horses by gender then age.'" ); gClassLabel = new JLabel( "Comparator class", JLabel.RIGHT ); gClassLabel.setFont( FONT_FOR_LABELS ); gClassLabel.setForeground( FOREGROUND_FOR_LABEL ); gClass = new JTextField( "InMyOrder", 30 ); gClass.setName( "gClass" ); gClass.setEditable( true ); gClass.setFont( FONT_FOR_LABELS ); gClass.setForeground( FOREGROUND_FOR_ENTER ); gClass.setBackground( BACKGROUND_FOR_EDITABLE ); gClass.setHorizontalAlignment( JTextField.LEFT ); gClass.setToolTipText( "class name of the generated Comparator, name of this variant way of comparing Objects/enums." ); createFieldPanelComponents(); resultsLabel = new JLabel( "Generated Java Code", JLabel.LEFT ); resultsLabel.setFont( FONT_FOR_LABELS ); resultsLabel.setForeground( FOREGROUND_FOR_LABEL ); generateButton = new JEButton( "Generate" ); generateButton.setToolTipText( "Generate Java source code" ); generateButton.setName( "generate" ); results = new JTextArea( "to come", 0, 0 ); results.setName( "results" ); results.setEditable( false ); results.setFont( FontFactory.build( "Dialog", Font.PLAIN, 15 ) ); results.setForeground( FOREGROUND_FOR_RESULT ); results.setToolTipText( "Generated Java code." ); resultsScrollPane = new JScrollPane( results, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED ); instructionsLabel = new JLabel( "Instructions", JLabel.LEFT ); instructionsLabel.setFont( FONT_FOR_LABELS ); instructionsLabel.setForeground( FOREGROUND_FOR_LABEL ); instructions = new JEditorPane(); instructions.setName( "instructions" ); instructions.setContentType( "text/html" ); // Setting background has no effect. You must set colours with HTML markup instructions.setBackground( BACKGROUND_FOR_INSTRUCTIONS ); instructions.setForeground( FOREGROUND_FOR_INSTRUCTIONS ); instructions.setFont( FONT_FOR_INSTRUCTIONS ); instructions.setAlignmentX( 0.5f ); instructions.setAlignmentY( 0.5f ); // provide some space around the inside instructions.setMargin( new Insets( 4, 4, 4, 4 ) ); instructions.setEnabled( true ); instructions.setEditable( false ); final HTMLDocument htmlDocument = new HTMLDocument(); instructions.setDocument( htmlDocument ); // do you setText after the setDocument, in compose. instructions.setText( "to come" ); instructionsScrollPane = new JScrollPane( instructions, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED ); hookListeners(); // Start display off at something interesting: compose(); }// /method /** * create components that will be places in the Field Panel */ private void createFieldPanelComponents() { fieldPanel = new JPanel(); fieldPanel.setLayout( new GridBagLayout() ); fieldPanel.setBackground( BACKGROUND_FOR_PANEL ); createFieldPanelLabels(); fieldNumbers = new JLabel[ MAX_FIELDS ]; fieldTypes = new JComboBox/**/[ MAX_FIELDS ]; fieldNames = new JTextField[ MAX_FIELDS ]; fieldIsDescendings = new JCheckBox[ MAX_FIELDS ]; fieldMoveUps = new JButton[ MAX_FIELDS ]; fieldMoveDowns = new JButton[ MAX_FIELDS ]; for ( int i = 0; i < MAX_FIELDS; i++ ) { fieldNumbers[ i ] = new JLabel( Integer.toString( i + 1 ) + "." ); fieldNumbers[ i ].setFont( FONT_FOR_LABELS ); fieldNumbers[ i ].setForeground( FOREGROUND_FOR_LABEL ); fieldTypes[ i ] = new JComboBox<>( FieldType.values() ); fieldTypes[ i ].setName( "fieldTypes[" + i + "]" ); // ensure displays all choices without scrolling. fieldTypes[ i ].setMaximumRowCount( FieldType.values().length ); // turn off the write-in feature fieldTypes[ i ].setEditable( false ); fieldTypes[ i ].setToolTipText( "Type of field to use in comparing." ); fieldIsDescendings[ i ] = new JCheckBox( "", false ); fieldIsDescendings[ i ].setName( "fieldIsDescendings[" + i + "]" ); // will toggle with DESCENDING_ICON fieldIsDescendings[ i ].setIcon( ASCENDING_ICON ); // text will change fieldIsDescendings[ i ].setToolTipText( "ascending or descending order" ); // better wording replaces // this later. fieldNames[ i ] = new JTextField( "to come", 30 ); fieldNames[ i ].setName( "fieldNames[" + i + "]" ); fieldNames[ i ].setEditable( true ); fieldNames[ i ].setFont( FONT_FOR_LABELS ); fieldNames[ i ].setForeground( FOREGROUND_FOR_ENTER ); fieldNames[ i ].setBackground( BACKGROUND_FOR_EDITABLE ); fieldNames[ i ].setHorizontalAlignment( JTextField.LEFT ); fieldNames[ i ].setToolTipText( "Name of field/getter to use in comparing, or leave it blank to compare the Object/enum as a " + "whole." ); fieldMoveUps[ i ] = new JButton(); fieldMoveUps[ i ].setName( "fieldMoveUps[" + i + "]" ); recordFieldRowIndex( fieldMoveUps[ i ], i ); if ( i == 0 ) { fieldMoveUps[ i ].setVisible( true ); fieldMoveUps[ i ].setEnabled( false ); } fieldMoveUps[ i ].setIcon( MOVE_UP_ICON ); fieldMoveUps[ i ].setDisabledIcon( MOVE_UP_DISABLED_ICON ); // suppress borders fieldMoveUps[ i ].setBorderPainted( false ); // suppress press decoration fieldMoveUps[ i ].setContentAreaFilled( false ); // suppress the ability of the button to be triggered by enter fieldMoveUps[ i ].setDefaultCapable( false ); // hide focus rectangle fieldMoveUps[ i ].setFocusPainted( false ); // set how wide the space must be around the text to the buttonBevelBorder. Also use ipadx/ejbd fieldMoveUps[ i ].setMargin( new Insets( 3, 3, 3, 3 ) ); // ignore clicks that come too fast fieldMoveUps[ i ].setMultiClickThreshhold( 100/* mill's */ ); // the buttons have transparent background fieldMoveUps[ i ].setOpaque( false ); fieldMoveUps[ i ].setToolTipText( "Click to move this field up to higher priority." ); fieldMoveDowns[ i ] = new JButton(); fieldMoveDowns[ i ].setName( "fieldMoveDowns[" + i + "]" ); recordFieldRowIndex( fieldMoveDowns[ i ], i ); if ( i == MAX_FIELDS - 1 ) { fieldMoveDowns[ i ].setVisible( true ); fieldMoveDowns[ i ].setEnabled( false ); } fieldMoveDowns[ i ].setIcon( MOVE_DOWN_ICON ); fieldMoveDowns[ i ].setDisabledIcon( MOVE_DOWN_DISABLED_ICON ); // suppress borders fieldMoveDowns[ i ].setBorderPainted( false ); // suppress press decoration fieldMoveDowns[ i ].setContentAreaFilled( false ); // suppress the ability of the button to be triggered by enter fieldMoveDowns[ i ].setDefaultCapable( false ); // hide focus rectangle fieldMoveDowns[ i ].setFocusPainted( false ); // set how wide the space must be around the text to the buttonBevelBorder. Also use ipadx/ejbd fieldMoveDowns[ i ].setMargin( new Insets( 3, 3, 3, 3 ) ); // ignore clicks that come too fast fieldMoveDowns[ i ].setMultiClickThreshhold( 100/* mill's */ ); // the buttons have transparent background fieldMoveDowns[ i ].setOpaque( false ); fieldMoveDowns[ i ].setToolTipText( "Click to move this field down to lower priority." ); } }// /method /** * create the Field Panel component Labels */ private void createFieldPanelLabels() { fieldTypeLabel = new JLabel( "Field Type", JLabel.CENTER ); fieldTypeLabel.setFont( FONT_FOR_LABELS_SMALL ); fieldTypeLabel.setForeground( FOREGROUND_FOR_LABEL ); fieldSortLabel = new JLabel( "Sort", JLabel.CENTER ); fieldSortLabel.setFont( FONT_FOR_LABELS_SMALL ); fieldSortLabel.setForeground( FOREGROUND_FOR_LABEL ); fieldAscDescLabel = new JLabel( "Direction", JLabel.CENTER ); fieldAscDescLabel.setFont( FONT_FOR_LABELS_SMALL ); fieldAscDescLabel.setForeground( FOREGROUND_FOR_LABEL ); fieldNameLabel = new JLabel( "Field/Getter Name", JLabel.CENTER ); fieldNameLabel.setFont( FONT_FOR_LABELS_SMALL ); fieldNameLabel.setForeground( FOREGROUND_FOR_LABEL ); fieldMoveLabel = new JLabel( "Move", JLabel.CENTER ); fieldMoveLabel.setFont( FONT_FOR_LABELS_SMALL ); fieldMoveLabel.setForeground( FOREGROUND_FOR_LABEL ); fieldMoveUpLabel = new JLabel( "Up", JLabel.CENTER ); fieldMoveUpLabel.setFont( FONT_FOR_LABELS_SMALL ); fieldMoveUpLabel.setForeground( FOREGROUND_FOR_LABEL ); fieldMoveDownLabel = new JLabel( "Down", JLabel.CENTER ); fieldMoveDownLabel.setFont( FONT_FOR_LABELS_SMALL ); fieldMoveDownLabel.setForeground( FOREGROUND_FOR_LABEL ); }// /method /** * install mildly customised instructions * * @param interfaceType Comparable or Comparator */ private void customiseInstructions( InterfaceType interfaceType ) { final FastCat sb = new FastCat( 3 ); sb.append( "" + "
    " + "
  1. Select whether you want to generate a Comparable or Comparator, nested or top-level.
  2. " + "
  3. Fill in the class name of the Objects being compared.
  4. " + "
  5. Fill in a comment about what the generated code will do e.g. " + "\"sort Customers geographically\".
  6. " ); if ( interfaceType == COMPARATOR_TOP_LEVEL || interfaceType == COMPARATOR_NESTED ) { sb.append( "
  7. Fill in the class name of the Comparator.
  8. " ); } sb.append( "
  9. For the first field relevant to the comparison, select the field type.
  10. " + "
  11. Select whether you want ascending" + " order or descending order by " + " clicking/toggling the corresponding sort direction green or red icon" + " (not the blue move up/down icons on the right).
  12. " + "
  13. Fill in the sort key field name/getter (without lead .) " + "e.g. width, getWidth(), custName.length(), prevVehicle " + "or leave it blank to compare the Object as a whole, possibly unboxed." + "
  14. " + "
  15. Repeat for tie-breaking secondary sort keys.
  16. " + "
  17. Use the up/down arrows to reorder the sort keys, if necessary.
  18. " + "
  19. To insert a key, add it at the bottom and move it up with the up arrow button beside it.
  20. " + "
  21. To delete a sort key, set its type to spare.
  22. " + "
  23. When you have the code the way you want, hit Generate.
  24. " + "
  25. Copy/paste the generated Java code into your own program.
" ); final String instructionsHTML = sb.toString(); try { instructions.setText( instructionsHTML ); } catch ( Exception e ) { err.println( "Because of Sun bug, unable to display instructions, please exit browser and restart" ); e.printStackTrace( err ); err.println( instructionsHTML ); } // Make sure the first line is visible. // invoke later to give time for text to render. SwingUtilities.invokeLater( () -> instructionsScrollPane.getVerticalScrollBar().setValue( 0 ) ); }// /method /** * debugging tool to display current value of GUI fields * * @param where where in code we are */ private void displayFields( String where ) { if ( DEBUGGING ) { out.println( ">>> " + where ); for ( int i = 0; i < MAX_FIELDS; i++ ) { // don't trim out.println( i + " : " + fieldTypes[ i ].getSelectedItem().toString() + " : [" + fieldNames[ i ] .getText() + "]" ); } } }// /method /** * Extract non-spare sort keys from the GUI fields * * @return extracted array of SortKey data. */ private SortKey[] extractUsedSortKeys() { // extract non-blank/spare lines. final ArrayList nonSpare = new ArrayList<>( MAX_FIELDS ); for ( int i = 0; i < MAX_FIELDS; i++ ) { final FieldType ft = ( FieldType ) fieldTypes[ i ].getSelectedItem(); if ( ft != FieldType.SPARE ) { String fieldName = fieldNames[ i ].getText().trim(); // allow empty fieldNames. nonSpare.add( new SortKey( ft, fieldIsDescendings[ i ].isSelected(), fieldName ) ); } } // end field loop return nonSpare.toArray( new SortKey[ nonSpare.size() ] ); } /** * hook up listeners to Components to detect changes to GUI fields. */ private void hookListeners() { final GeneralListener generalListener = new GeneralListener(); final DescendingToggleListener descendingToggleListener = new DescendingToggleListener(); final LostFocusListener lostFocusListener = new LostFocusListener(); final MoveUpListener moveUpListener = new MoveUpListener(); final MoveDownListener moveDownListener = new MoveDownListener(); interfaceSelection.addItemListener( generalListener ); javaVersionBox.addItemListener( generalListener ); useNullSafeBox.addItemListener( generalListener ); oClass.addActionListener( generalListener ); oClass.addFocusListener( lostFocusListener ); comment.addActionListener( generalListener ); comment.addFocusListener( lostFocusListener ); gClass.addActionListener( generalListener ); generateButton.addActionListener( generalListener ); for ( int i = 0; i < MAX_FIELDS; i++ ) { fieldTypes[ i ].addActionListener( generalListener ); fieldIsDescendings[ i ].addItemListener( descendingToggleListener ); fieldNames[ i ].addActionListener( generalListener ); fieldNames[ i ].addFocusListener( lostFocusListener ); fieldMoveUps[ i ].addActionListener( moveUpListener ); fieldMoveDowns[ i ].addActionListener( moveDownListener ); } }// /method /** * add fields to layout * * @param contentPane where to put the fields */ private void layoutApplet( Container contentPane ) { // layout // ----0---------1----- 2---- 3 // 0 ----------title --title2--- // 1 "Generate"-- able jver ~jver // 2 "Oclass"-- Oclass--------- // 4 "Comment"-- comment ------ // 5 "Gclass---- class--- // 6 fieldPanel --------------- // 7 "Generated" --- GENERATE // 8 results----------------- // 9 "instructions" //10 instructions ------------- int line = 0; // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( title, new GridBagConstraints( 0, line, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 10, 10, 10, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( title2, new GridBagConstraints( 2, line, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 10, 5, 10, 10 ), 0, 0 ) ); line++; // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( generateLabel, new GridBagConstraints( 0, line, 1, 1, 1.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 0, 10, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( interfaceSelection, new GridBagConstraints( 1, line, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 0, 5, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( javaVersionBox, new GridBagConstraints( 2, line, 1, 1, 1.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 0, 5, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( javaVersionLabel, new GridBagConstraints( 3, line, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 0, 5, 5, 5 ), 0, 0 ) ); line++; // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( oClassLabel, new GridBagConstraints( 0, line, 1, 1, 1.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 0, 10, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( oClass, new GridBagConstraints( 1, line, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 0, 5, 5, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( useNullSafeBox, new GridBagConstraints( 3, line, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 0, 5, 5, 5 ), 0, 0 ) ); line++; // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( commentLabel, new GridBagConstraints( 0, line, 1, 1, 1.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 0, 10, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( comment, new GridBagConstraints( 1, line, 3, 1, 90.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 0, 5, 5, 10 ), 0, 0 ) ); line++; // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( gClassLabel, new GridBagConstraints( 0, line, 1, 1, 1.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 0, 10, 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( gClass, new GridBagConstraints( 1, line, 2, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 0, 5, 5, 10 ), 0, 0 ) ); line++; // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( fieldPanel, new GridBagConstraints( 0, line, 4, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 5, 10, 10, 10 ), 0, 0 ) ); layoutFieldPanel(); int belowLine = line + 1; // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( resultsLabel, new GridBagConstraints( 0, belowLine, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 10, 5, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( generateButton, new GridBagConstraints( 3, belowLine++, 1, 1, 1.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 0, 5, 5, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( resultsScrollPane, new GridBagConstraints( 0, belowLine++, 4, 1, 1.0, 75.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 10, 5, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( instructionsLabel, new GridBagConstraints( 0, belowLine++, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 10, 5, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady contentPane.add( instructionsScrollPane, new GridBagConstraints( 0, belowLine, 4, 1, 1.0, 30.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 10, 10, 10 ), 0, 0 ) ); }// /method /** * layout the field panel, rows of sort key fields. */ private void layoutFieldPanel() { // layout // --------------------------------------- // 0 1 2 3 4 5 // 0 --"type" "dir" "field" "up" "down"--- // 1 n. type asc/desc fname- up- --dn -- layoutFieldPanelLabels(); for ( int i = 0; i < MAX_FIELDS; i++ ) { final int sortKeyLine = i + 2; fieldPanel.add( fieldNumbers[ i ], new GridBagConstraints( 0, sortKeyLine, 1, 1, 1.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 0, 10, i < MAX_FIELDS - 1 ? 2 : 5, 5 ), 0, 0 ) ); fieldPanel.add( fieldTypes[ i ], new GridBagConstraints( 1, sortKeyLine, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets( 0, 5, i < MAX_FIELDS - 1 ? 2 : 5, 5 ), 0, 0 ) ); fieldPanel.add( fieldIsDescendings[ i ], new GridBagConstraints( 2, sortKeyLine, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets( 0, 5, i < MAX_FIELDS - 1 ? 1 : 4, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldNames[ i ], new GridBagConstraints( 3, sortKeyLine, 1, 1, 100.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 0, 5, i < MAX_FIELDS - 1 ? 2 : 5, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldMoveUps[ i ], new GridBagConstraints( 4, sortKeyLine, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 5, i < MAX_FIELDS - 1 ? 1 : 4, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldMoveDowns[ i ], new GridBagConstraints( 5, sortKeyLine, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 5, 1, 10 ), 0, 0 ) ); } // end of fieldPanel }// /method /** * layout the field panel labels for the sort key fields */ private void layoutFieldPanelLabels() { // layout // --------------------------------------- // 0 1 2 3 4 5 // sort // 0 --"type" "direction" "field" "up" "down"--- // 1 n. type asc/desc fname- up- --dn -- final int topLabelLine = 0; final int bottomLabelLine = 1; // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldTypeLabel, new GridBagConstraints( 1, bottomLabelLine, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets( 0, 5, 2, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldSortLabel, /* sort */ new GridBagConstraints( 2, topLabelLine, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets( 0, 5, 2, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldAscDescLabel, new GridBagConstraints( 2, bottomLabelLine, 1, 1, 1.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets( 0, 5, 2, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldNameLabel, new GridBagConstraints( 3, bottomLabelLine, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 5, 2, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldMoveLabel, /* move */ new GridBagConstraints( 4, topLabelLine, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets( 0, 5, 2, 10 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldMoveUpLabel, new GridBagConstraints( 4, bottomLabelLine, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets( 0, 5, 2, 5 ), 0, 0 ) ); // x y w h wtx wty anchor fill T L B R padx pady fieldPanel.add( fieldMoveDownLabel, new GridBagConstraints( 5, bottomLabelLine, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets( 0, 5, 2, 10 ), 0, 0 ) ); }// /method /** * swap GUI field data in rows i and j * * @param i first row of fields * @param j second row of fields */ private void swapFieldRows( final int i, final int j ) { // we don't swap the components. They have layout info attached. We just swap the data they contain. // Alternatively we could have used a JTable and used its general purpose logic. if ( defibrillate ) { return; } defibrillate = true; // setSelectedItem following will trigger Events that we want to ignore. final FieldType swap1a = ( FieldType ) fieldTypes[ i ].getSelectedItem(); final FieldType swap1b = ( FieldType ) fieldTypes[ j ].getSelectedItem(); fieldTypes[ i ].setSelectedItem( swap1b ); fieldTypes[ j ].setSelectedItem( swap1a ); final boolean swap2a = fieldIsDescendings[ i ].isSelected(); final boolean swap2b = fieldIsDescendings[ j ].isSelected(); fieldIsDescendings[ i ].setSelected( swap2b ); fieldIsDescendings[ j ].setSelected( swap2a ); final String swap3a = fieldNames[ i ].getText(); // we don't trim. final String swap3b = fieldNames[ j ].getText(); fieldNames[ i ].setText( swap3b ); fieldNames[ j ].setText( swap3a ); // we don't move the number column or the move up/down buttons. defibrillate = false; }// /method /** * Tidy GUI fields by making them visible/enabled/erased. */ private void tidyFields() { if ( DEBUGGING ) { displayFields( "starting tidyFields" ); } InterfaceType interfaceType = ( InterfaceType ) interfaceSelection.getSelectedItem(); switch ( interfaceType ) { case COMPARABLE: interfaceSelection.setToolTipText( "Generate a Comparable class" ); gClassLabel.setText( "Comparable class" ); gClassLabel.setVisible( false ); gClass.setVisible( false ); break; case COMPARATOR_TOP_LEVEL: interfaceSelection.setToolTipText( "Generate a Comparator top-level class" ); gClassLabel.setText( "Comparator class" ); gClassLabel.setVisible( true ); gClass.setVisible( true ); break; case COMPARATOR_NESTED: interfaceSelection.setToolTipText( "Generate a Comparator nested class" ); gClassLabel.setText( "Comparator class" ); gClassLabel.setVisible( true ); gClass.setVisible( true ); break; } for ( int i = 0; i < MAX_FIELDS; i++ ) { final FieldType ft = ( FieldType ) fieldTypes[ i ].getSelectedItem(); final String fieldName = fieldNames[ i ].getText().trim(); if ( ft == FieldType.SPARE ) { fieldIsDescendings[ i ].setEnabled( false ); fieldIsDescendings[ i ].setVisible( false ); fieldNames[ i ].setEnabled( false ); fieldNames[ i ].setVisible( false ); if ( fieldName.length() > 0 ) { // avoid clearing unnecessarily to prevent generating unnecessary events. fieldNames[ i ].setText( "" ); } } else { // has actual type, but perhaps not yet a field name fieldIsDescendings[ i ].setEnabled( true ); fieldIsDescendings[ i ].setVisible( true ); fieldNames[ i ].setEnabled( true ); fieldNames[ i ].setVisible( true ); } } // end field loop customiseInstructions( interfaceType ); }// /method /** * Allow this Applet to run as as application as well. * * @param args command line arguments ignored. */ public static void main( String args[] ) { HybridJ.fireup( new ComparatorCutter(), TITLE_STRING + " " + VERSION_STRING, APPLET_WIDTH, APPLET_HEIGHT ); }// /method /** * Called by the browser or Applet viewer to inform * this Applet that it is being reclaimed and that it should destroy * any resources that it has allocated. */ public void destroy() { comment = null; commentLabel = null; fieldAscDescLabel = null; fieldIsDescendings = null; fieldMoveDownLabel = null; fieldMoveDowns = null; fieldMoveLabel = null; fieldMoveUpLabel = null; fieldMoveUps = null; fieldNameLabel = null; fieldNames = null; fieldNumbers = null; fieldPanel = null; fieldSortLabel = null; fieldTypeLabel = null; fieldTypes = null; gClass = null; gClassLabel = null; generateButton = null; generateLabel = null; instructions = null; instructionsLabel = null; instructionsScrollPane = null; interfaceSelection = null; oClass = null; oClassLabel = null; results = null; resultsLabel = null; resultsScrollPane = null; title = null; title2 = null; javaVersionLabel = null; javaVersionBox = null; useNullSafeBox = null; }// /method /** * Called by the browser or Applet viewer to inform * this Applet that it has been loaded into the system. */ @Override public void init() { // needs 1.2 to use Entities which use ArrayList. if ( !VersionCheck.isJavaVersionOK( 1, 8, 0, this ) ) { // effectively abort return; } buildMenu(); // also initial L&F Container contentPane = this.getContentPane(); contentPane.setBackground( BACKGROUND_FOR_APPLET ); contentPane.setLayout( new GridBagLayout() ); createComponents(); layoutApplet( contentPane ); this.validate(); this.setVisible( true ); }// /method // /methods /** * triggered if one of the descending/ascending field buttons is toggled */ private class DescendingToggleListener implements ItemListener { public void itemStateChanged( ItemEvent e ) { // warning fieldIsDescendings[i].getSelection() returns a ButtonModel not the // JCheckBox. // Beware, you will get two events for each change, one to // remove a selection // and one to add a new one. if ( DEBUGGING ) { out.println( "Descending toggle itemStateChanged " + getName( e ) ); } JCheckBox isFieldDescending = ( JCheckBox ) e.getSource(); if ( isFieldDescending.isSelected() ) { isFieldDescending.setIcon( DESCENDING_ICON ); isFieldDescending.setToolTipText( "This field will be sorted in descending order (largest first). Click to toggle to ascending " + "order." ); } else { isFieldDescending.setIcon( ASCENDING_ICON ); isFieldDescending.setToolTipText( "This field will be sorted in ascending order (smallest first). Click to toggle to descending" + " order." ); } compose(); } } /** * general listener than is triggered if we need to recompose the generated text */ private class GeneralListener implements ActionListener, ItemListener { public void actionPerformed( final ActionEvent e ) { if ( DEBUGGING ) { out.println( "actionPerformed " + getName( e ) ); } compose(); } public void itemStateChanged( ItemEvent e ) { // warning comparatorButton.getSelection() returns a ButtonModel not the // JCheckBox. // Beware, you will get two events for each change, one to // remove a selection // and one to add a new one. if ( DEBUGGING ) { out.println( "itemStateChanged " + getName( e ) ); } compose(); } } /** * triggered if use clicks away from one of the text fields. */ private class LostFocusListener extends FocusAdapter { @Override public void focusLost( final FocusEvent e ) { if ( DEBUGGING ) { out.println( "focustLost " + getName( e ) ); } super.focusLost( e ); compose(); } } /** * handles events when user clicks a move down icon */ private class MoveDownListener implements ActionListener { public void actionPerformed( final ActionEvent e ) { // request to move this item down one slot. i.e to swap it with slot one below. if ( DEBUGGING ) { out.println( "moveDown " + getName( e ) ); } JButton moveFieldDown = ( JButton ) e.getSource(); final int i = retrieveFieldRowIndex( moveFieldDown ); swapFieldRows( i, i + 1 ); compose(); } } /** * handles events when user clicks a move up icon */ private class MoveUpListener implements ActionListener { public void actionPerformed( final ActionEvent e ) { // request to move this item up one slot. i.e to swap it with slot one above. // We disabled the 0 up button so we don't have to deal with that. if ( DEBUGGING ) { out.println( "moveUp " + getName( e ) ); } JButton moveFieldUp = ( JButton ) e.getSource(); final int i = retrieveFieldRowIndex( moveFieldUp ); swapFieldRows( i, i - 1 ); compose(); } } }