[rfc][icedtea-web] Certificate viewer cleanup

Jiri Vanek jvanek at redhat.com
Fri Sep 6 02:40:17 PDT 2013


On 08/29/2013 05:18 PM, Jacob Wisor wrote:
> "Jacob Wisor"<gitne at xxxxxxxxxxxx> wrote:
>> >"Jacob Wisor"<gitne at xxxxxxxxxxxx> wrote:
>>> > >* Replaced old insufficient certificate viewer's action buttons handling with proper handling
>>> > >* Made System-level certificates managable if current user has write access to a KeyStore (usually only root or admin)
>>> > >* Added informative tool tip to disabled buttons when certificates are unmanagable
>>> > >* Made certificate table uneditable
>>> > >* Made certificate info table uneditable
>>> > >* Made certificate info's descriptive JTextField have the same font size as its info table for better readability
>>> > >* Added certificate file filters with Windows/Linux preference
>>> > >* Added auto-suggested certificate file names when exporting
>>> > >* Added automatically generated mnemonics
>>> > >* Made labels link to corresponding JComponents
>>> > >* Added a border to certificate type label for better readability
>>> > >* Uniformed border sizes
>>> > >* Added removing of multiple certificates at once
>>> > >* Turned some only once occuring fields into static
>>> > >* Added proper exception handling when setting system look & feel, hence removed superfluous "throws Exception" from method signatures
>>> > >
>>> > >Happy reviewing!
>>> > >
>>> > >Jacob
>> >
>> >Pasting in this time.
>> >
>> >[...]
> I have realized I have missed to add a new file to the patch. I have updated the copyright headers too.
> 

Nice job, thank you! I have finally read it through!


As drawback there is bunch of exceptions:
java.io.EOFException
	at java.io.DataInputStream.readInt(DataInputStream.java:392)
	at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:645)
	at sun.security.provider.JavaKeyStore$JKS.engineLoad(JavaKeyStore.java:55)
	at java.security.KeyStore.load(KeyStore.java:1214)
	at net.sourceforge.jnlp.security.KeyStores.createKeyStoreFromFile(KeyStores.java:416)
	at net.sourceforge.jnlp.security.KeyStores.getKeyStore(KeyStores.java:140)
	at net.sourceforge.jnlp.security.KeyStores.getKeyStore(KeyStores.java:119)
	at net.sourceforge.jnlp.security.KeyStores.getClientKeyStores(KeyStores.java:235)
	at net.sourceforge.jnlp.security.VariableX509TrustManager.<init>(VariableX509TrustManager.java:145)
	at
net.sourceforge.jnlp.security.VariableX509TrustManager.getInstance(VariableX509TrustManager.java:439)
	at
net.sourceforge.jnlp.security.VariableX509TrustManagerJDK7.<init>(VariableX509TrustManagerJDK7.java:53)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
	at net.sourceforge.jnlp.runtime.JNLPRuntime.getSSLSocketTrustManager(JNLPRuntime.java:319)
	at net.sourceforge.jnlp.runtime.JNLPRuntime.initialize(JNLPRuntime.java:256)
	at
net.sourceforge.jnlp.security.viewer.CertificateViewer.showCertificateViewer(CertificateViewer.java:103)
	at net.sourceforge.jnlp.security.viewer.CertificateViewer.main(CertificateViewer.java:126)
	at net.sourceforge.jnlp.runtime.Boot.main(Boot.java:135)




and (more important)

java.lang.NullPointerException
	at net.sourceforge.jnlp.security.viewer.CertificatePane.readKeyStore(CertificatePane.java:316)
	at net.sourceforge.jnlp.security.viewer.CertificatePane.repopulateTables(CertificatePane.java:344)
	at net.sourceforge.jnlp.security.viewer.CertificatePane.access$1200(CertificatePane.java:90)
	at
net.sourceforge.jnlp.security.viewer.CertificatePane$CertificateTypeListener.actionPerformed(CertificatePane.java:408)
	at javax.swing.JComboBox.fireActionEvent(JComboBox.java:1260)
	at javax.swing.JComboBox.setSelectedItem(JComboBox.java:588)
	at javax.swing.JComboBox.setSelectedIndex(JComboBox.java:624)
	at javax.swing.plaf.basic.BasicComboPopup$Handler.mouseReleased(BasicComboPopup.java:835)
	at java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:290)
	at java.awt.Component.processMouseEvent(Component.java:6505)
	at javax.swing.JComponent.processMouseEvent(JComponent.java:3312)
	at javax.swing.plaf.basic.BasicComboPopup$1.processMouseEvent(BasicComboPopup.java:499)
	at java.awt.Component.processEvent(Component.java:6270)
	at java.awt.Container.processEvent(Container.java:2229)
	at java.awt.Component.dispatchEventImpl(Component.java:4861)
	at java.awt.Container.dispatchEventImpl(Container.java:2287)
	at java.awt.Component.dispatchEvent(Component.java:4687)
	at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4832)
	at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4492)
	at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4422)
	at java.awt.Container.dispatchEventImpl(Container.java:2273)
	at java.awt.Window.dispatchEventImpl(Window.java:2719)
	at java.awt.Component.dispatchEvent(Component.java:4687)
	at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:735)
	at java.awt.EventQueue.access$200(EventQueue.java:103)
	at java.awt.EventQueue$3.run(EventQueue.java:694)
	at java.awt.EventQueue$3.run(EventQueue.java:692)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87)
	at java.awt.EventQueue$4.run(EventQueue.java:708)
	at java.awt.EventQueue$4.run(EventQueue.java:706)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:705)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:154)
	at java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:182)
	at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:251)
	at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:733)
	at java.awt.EventQueue.access$200(EventQueue.java:103)
	at java.awt.EventQueue$3.run(EventQueue.java:694)
	at java.awt.EventQueue$3.run(EventQueue.java:692)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:703)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:91)


Both are quite serious as they stop the redrawing of table.


Two ideas - when digging in this, maybe to switch  ~/icedtea-web-image/bin/javaws  -viewer from
Dialog to Frame would be nice - at least it will be accesible via alt+tab and maximiseable and so on...
- the tool tip text are pretty good {except {0} error) but are hard to be seen. Most people do not
hover over disabled button. It would be nice to have them as label on visible(if useful)/invisible
paanel eg. between buttons and table.

Few nits in inline:


> 
> CertficateViewer_cleanup.patch
> 
> 
> diff -r 420d72e5cee7 netx/net/sourceforge/jnlp/resources/Messages.properties
> --- a/netx/net/sourceforge/jnlp/resources/Messages.properties
> +++ b/netx/net/sourceforge/jnlp/resources/Messages.properties
> @@ -275,6 +276,9 @@
>   SValidity=Validity
>   
>   # Certificate Viewer
> +CVCannotImport=You can not import certificates of this type because {0}

Those {0} are never avaluated.

> +CVCannotRemove=You can not remove certificates of this type because {0}

The  CVCannotImport/CVCannotRemove seems to me randomly switched with CVCause.
CVCannotImport/CVCannotRemove - are shown when I change certificate type, whether
CVCause is shown when  user/system tab.

I think both are valid at all time, so their concation would be nice.
> +CVCause=<ul><li>you are not logged in as an administrator,</li><li>you have no file system write permission to the keystore file,</li><li>the keystore file is inaccesible via network,</il><li>the keystore file is write protected by file system attributes,</li><li>you do not have a <code>FilePermission("write")</code>, or</li><li>the keystore file is otherwise physically inaccesible.</li></ul>
>   CVCertificateViewer=Certificates
>   CVCertificateType=Certificate Type
>   CVDetails=Details

...snip...

> @@ -302,6 +303,37 @@
>       }
>   
>       /**
> +     * Checks whether the specified system-level or user-level {@link KeyStore}
> +     * is writable.
> +     * @param level
> +     * @return {@code true} if the specified {@code KeyStore} is writable,
> +     * {@code false} otherwise
> +     * @see KeyStores.Level
> +     */
> +    public static final boolean isKeyStoreWriteable(KeyStores.Level level, KeyStores.Type type) {
> +        if (level == null) throw new IllegalArgumentException("level == " + level);
> +        if (type == null) throw new IllegalArgumentException("type == " + type);
> +
> +        FileOutputStream fileOutputStream = null;
> +
> +        try {
> +            fileOutputStream = new FileOutputStream(KeyStores.getKeyStoreLocation(level, type), true);
> +        } catch (final FileNotFoundException fileNotFoundException) {
> +            fileOutputStream = null;
> +        } catch (final SecurityException securityException) {
> +            fileOutputStream = null;
> +        } finally {
> +            if (fileOutputStream != null) try {
> +                fileOutputStream.close();
> +            } catch (final IOException ioException) {
> +                // Don't bother, fileOutputStream is probably closed anyway
> +            }
> +        }
> +
> +        return fileOutputStream != null;


Its really enough? I did not know this "hack". Interesting :)

Please add unittest for anyway.
> +    }
> +
...snip...
>        * "Issued To" and "Issued By" string pairs for certs.
>        */
>       private String[][] issuedToAndBy = null;
> -    private final String[] columnNames = { R("CVIssuedTo"), R("CVIssuedBy") };
> +    private final static String[] columnNames = { R("CVIssuedTo"), R("CVIssuedBy") };
> +    private final static String CER_EXTENSION = "cer",
> +                                CRT_EXTENSION = "crt",
> +                                PFX_EXTENSION = "pfx",
> +                                P12_EXTENSION = "p12",
> +                                PKCS12 = "PKCS #12";

I must say I'm quite confused by this extensions. What are they needed for now? (as they were not
needed before)
>   
> -    private final CertificateType[] certificateTypes = new CertificateType[] {
> +    private final static CertificateType[] certificateTypes = new CertificateType[] {
...
>   
>           userTable = new JTable(null);
>           systemTable = new JTable(null);
> -        disableForSystem = new ArrayList<JComponent>();

Well I'm not sure about this  expansion of array into four separated variables.
Althoug disableForSystem will become really irrelevant name.

I would go with JComponent[] button = new JComponent{
> +        this.importButton = new JButton();
> +        this.exportButton = new JButton();
> +        this.removeButton = new JButton();
> +        this.detailButton = new JButton();
}

As I believe there are few cases where iteration will win over duplicate code.
>   
>           addComponents();
>   
> @@ -139,6 +167,9 @@
>           } else {
>               currentKeyStoreLevel = Level.SYSTEM;
>           }
> +        this.importButton.setEnabled(this.isKeyStoreWriteable =
> +                KeyStores.isKeyStoreWriteable(this.currentKeyStoreLevel,
> +                        this.currentKeyStoreType));
>   
>           repopulateTables();
>       }
> @@ -163,9 +194,11 @@
>           certificateTypePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
>   
>           JLabel certificateTypeLabel = new JLabel(R("CVCertificateType"));
> +        certificateTypeLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
>   
>           certificateTypeCombo = new JComboBox(certificateTypes);
>           certificateTypeCombo.addActionListener(new CertificateTypeListener());
> +        certificateTypeLabel.setLabelFor(this.certificateTypeCombo);
>   
>           certificateTypePanel.add(certificateTypeLabel, BorderLayout.LINE_START);
>           certificateTypePanel.add(certificateTypeCombo, BorderLayout.CENTER);
> @@ -173,20 +206,24 @@
>           JPanel tablePanel = new JPanel(new BorderLayout());
>   
>           // User Table
> -        DefaultTableModel userTableModel = new DefaultTableModel(issuedToAndBy, columnNames);
> -        userTable.setModel(userTableModel);
> +        userTable.setModel(new NonEditableTableModel(issuedToAndBy, CertificatePane.columnNames));
>           userTable.getTableHeader().setReorderingAllowed(false);
>           userTable.setFillsViewportHeight(true);
> +        final CertificatePane.CertificateListSelectionListener certificateListSelectionListener;
> +        userTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
> +        userTable.getSelectionModel().addListSelectionListener(certificateListSelectionListener =
> +                new CertificatePane.CertificateListSelectionListener());
>           JScrollPane userTablePane = new JScrollPane(userTable);
>           userTablePane.setPreferredSize(TABLE_DIMENSION);
>           userTablePane.setSize(TABLE_DIMENSION);
>           userTablePane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
>   
>           // System Table
> -        DefaultTableModel systemTableModel = new DefaultTableModel(issuedToAndBy, columnNames);
> -        systemTable.setModel(systemTableModel);
> +        systemTable.setModel(new NonEditableTableModel(issuedToAndBy, CertificatePane.columnNames));
>           systemTable.getTableHeader().setReorderingAllowed(false);
>           systemTable.setFillsViewportHeight(true);
> +        systemTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
> +        systemTable.getSelectionModel().addListSelectionListener(certificateListSelectionListener);
>           JScrollPane systemTablePane = new JScrollPane(systemTable);
>           systemTablePane.setPreferredSize(TABLE_DIMENSION);
>           systemTablePane.setSize(TABLE_DIMENSION);
> @@ -199,35 +236,50 @@
>   
>           JPanel buttonPanel = new JPanel(new FlowLayout());
>   
> -        String[] buttonNames = { R("CVImport"), R("CVExport"), R("CVRemove"), R("CVDetails") };
> -        char[] buttonMnemonics = { KeyEvent.VK_I,
> -                                                                        KeyEvent.VK_E,
> -                                                                        KeyEvent.VK_M,
> -                                                                        KeyEvent.VK_D };
> -        ActionListener[] listeners = { new ImportButtonListener(),
> -                                                                                new ExportButtonListener(),
> -                                                                                new RemoveButtonListener(),
> -                                                                                new DetailsButtonListener() };
> -        JButton button;
> +        final String[] controlTexts = {
> +            R("CVCertificateType"),
> +            R("CVImport"),
> +            R("CVExport"),
> +            R("CVRemove"),
> +            R("CVDetails"),
> +            R("ButClose")
> +        };
> +        final Map<String, Character> mnemonicsMap = UI.generateMnemonics(controlTexts);
> +        certificateTypeLabel.setDisplayedMnemonic(mnemonicsMap.get(R("CVCertificateType")));
>   
>           //get the max width
>           int maxWidth = 0;
> -        for (int i = 0; i < buttonNames.length; i++) {
> -            button = new JButton(buttonNames[i]);
> +        for (int i = 1; i < 5; i++) {

buttonNames.length is definitively better then "5".
> +            JButton button = new JButton(controlTexts[i]);
>               maxWidth = Math.max(maxWidth, button.getMinimumSize().width);
>           }
>   
> -        for (int i = 0; i < buttonNames.length; i++) {
> -            button = new JButton(buttonNames[i]);
> -            button.setMnemonic(buttonMnemonics[i]);
> -            button.addActionListener(listeners[i]);
> -            button.setSize(maxWidth, button.getSize().height);
> -            // import and remove buttons
> -            if (i == 0 || i == 2) {
> -                disableForSystem.add(button);
> -            }
> -            buttonPanel.add(button);
> -        }

Yup, here is the duplicated code. I know there will be some ifs, but really iteration is preferred
 way.
> +        this.importButton.setText(controlTexts[1]);
> +        this.importButton.setMnemonic(mnemonicsMap.get(controlTexts[1]));
> +        this.importButton.addActionListener(new CertificatePane.ImportButtonListener());
> +        this.importButton.setSize(maxWidth, this.importButton.getSize().height);
> +        buttonPanel.add(this.importButton);
> +
> +        this.exportButton.setText(controlTexts[2]);
> +        this.exportButton.setMnemonic(mnemonicsMap.get(controlTexts[2]));
> +        this.exportButton.addActionListener(new CertificatePane.ExportButtonListener());
> +        this.exportButton.setSize(maxWidth, this.exportButton.getSize().height);
> +        this.exportButton.setEnabled(false);
> +        buttonPanel.add(this.exportButton);
> +
> +        this.removeButton.setText(controlTexts[3]);
> +        this.removeButton.setMnemonic(mnemonicsMap.get(controlTexts[3]));
> +        this.removeButton.addActionListener(new CertificatePane.RemoveButtonListener());
> +        this.removeButton.setSize(maxWidth, this.removeButton.getSize().height);
> +        this.removeButton.setEnabled(false);
> +        buttonPanel.add(this.removeButton);
> +
> +        this.detailButton.setText(controlTexts[4]);
> +        this.detailButton.setMnemonic(mnemonicsMap.get(controlTexts[4]));
> +        this.detailButton.addActionListener(new CertificatePane.DetailsButtonListener());
> +        this.detailButton.setSize(maxWidth, this.detailButton.getSize().height);
> +        this.detailButton.setEnabled(false);
> +        buttonPanel.add(this.detailButton);
>   
>           tablePanel.add(tabbedPane, BorderLayout.CENTER);
>           tablePanel.add(buttonPanel, BorderLayout.SOUTH);
...snip...
>   
> @@ -112,12 +113,16 @@
>       private static void setSystemLookAndFeel() {
>           try {
>               UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
> -        } catch (Exception e) {
> +        } catch (ClassNotFoundException classNotFoundException) {}
> +          catch (InstantiationException instantiationException) {}
> +          catch (IllegalAccessException illegalAccessException) {}
> +          catch (UnsupportedLookAndFeelException unsupportedLookAndFeelException) {}
> +          catch (ClassCastException classCastException) {
>               // don't worry if we can't.
>           }

Well, not sure if worthy, but as you wish. I would be proably fan of ex.printstackTrace in case of
debug on. But I do not isnisits.
>       }
>   
> -    public static void main(String[] args) throws Exception {
> +    public static void main(String[] args) {
>           CertificateViewer.showCertificateViewer();
>       }
...
> +/**
> + * Impementation of a certificate viewer.
> + * <p>
> + *     The certificate viewer can be accessed via the "Certificates" tab or by

I would add
 *     The certificate viewer can be accessed via the "Certificates" tab in itw-settings or by

But do not isnists.
> + *     invoking {@code javaws -viewer} on the command line.
> + * </p>
> + * @see net.sourceforge.jnlp.controlpanel.ControlPanel
> + */
...snip...
> +
> +/**
> + * This class provides methods and classes for common and recurring tasks while
> + * dealing with the UI that are not provided by the Java SE platform.
> + * @since 1.5
> + */
> +public final class UI {
> +    /**
> +     * Hide the default constructor from instantiating this class, because it is
> +     * a mere collection of diverese helper functionalities encapsulated in either
> +     * static methods or static nested classes.
> +     */
> +    private UI() {
> +    }
> +
> +    /**
> +     * Generates corresponding mnemonics to texts of controls like buttons,
> +     * labels, check boxes, radio buttons, or menu items. This method is primarily
> +     * useful for applications which control's texts are loaded from localized
> +     * resources that either lack mnemonics for corresponding controls or when
> +     * storing mnemonics in resources is not desired.
> +     * <p align="justify"><b>NOTE:</b><br/>
> +     * This method works with Latin, Greek, and Cyrillic alphabets only!</p>
> +     * This method may produce multiple identical (recurring) mnemonics if a text
> +     * does not have enough distinct letters.
> +     * @todo Add support for right-to-left alphabets and other scripts.
> +     * @param strings A {@link String} array with all Window's or Dialog's
> +     * control's texts that shall get mnemonics
> +     * @return a {@link HashMap} of texts with their corresponding mnemonic characters
> +     */

Ugh. Although the idea behind this is really _excelent_ I  doubt it a bit.Or I doubt its complexity.

Before scratching it - few topics:
 - is really mnemoic based on localisation desired behaviour? I'm in favour that it is not. Mostly
one simpli look into menu, and when he used this click and find click to often, he remebers the
mneonic. Maybe it is even better when all alnguages have them same. But you know more about
localisations then me.
- the implementation should allwo default values - especialy for not "Latin, Greek, and Cyrillic "
or strings which  "does not have enough distinct letters"
- I think that the implementation is too complicated according what it do (see inline)
- first letters of words should be preffered
- it will need unittest;)


I'm in grete favour to remove this from this changeset and proceed it as separate patch.

> +    public static Map<String, Character> generateMnemonics(final String[] strings) {
> +        if (strings == null) throw new IllegalArgumentException("strings");

At lest "input string was null" ;)
> +
> +        final int stringsLength = strings.length;
> +        final HashMap<String, Character> hashMap = new HashMap<String, Character>(stringsLength);
This to shoudl be  final Map<String, Character> hashMap = new HashMap...

> +        for (int index = 0; index < stringsLength; index++) {
> +            // Remove all white spaces, because they must not be mnemonics

maybe   if (strings[index] == null) throw new IllegalArgumentException("strings") too?

> +            final String s = strings[index], reducedString = s.replaceAll("[^A-Za-z0-9\u0391-\u03A9\u03B1-\u03C9\u0410-\u045F]" /*"[\\s\\p{Punct}\\p{Cntrl}\u0500-\uFFFF]"*/, "");
s.replaceAll("/s","") should do the job ;)
 - If you really wont to remove only whitespaces, but I see different in regex.
 - Maybe jsut keep [A-Z,a-z] would be more usefull

> +            char mnemonic = '\u0000';
> +            final int reducedStringLength = reducedString.length();
> +            for (int charIndex = 0; charIndex < reducedStringLength && hashMap.containsValue(mnemonic = Character.toUpperCase(reducedString.charAt(charIndex))); charIndex++);
interesting line :)
> +            hashMap.put(s, mnemonic);
> +        }
> +
> +        return hashMap;
> +    }
> +


I would probably move this to separate calss file.

Also it would be nice to have some smoke test for this, but as I'm not sure of comlexity I do not
isnists.

> +    /**
> +     * A table model that is in effect a {@link DefaultTableModel} except for no
> +     * cell being editable.
> +     * @see DefaultTableModel
> +     */
> +    public static class NonEditableTableModel extends DefaultTableModel {
> +        /**
> +         * Constructs a {@link javax.swing.table.TableModel} that serves only one
> +         * purpose: make cells of certificate tables not editable.
> +         * @see DefaultTableModel#DefaultTableModel()
> +         */
> +        public NonEditableTableModel() {
> +            super();
> +        }
> +
> +        /**
> +         * Constructs a {@link javax.swing.table.TableModel} that serves only one
> +         * purpose: make cells of certificate tables not editable.
> +         * @param rowCount the number of rows the table holds
> +         * @param columnCount the number of columns the table holds
> +         * @see DefaultTableModel#DefaultTableModel(int,int)
> +         */
> +        public NonEditableTableModel(final int rowCount, final int columnCount) {
> +            super(rowCount, columnCount);
> +        }
> +
> +        /**
> +         * Constructs a {@link javax.swing.table.TableModel} that serves only one
> +         * purpose: make cells of certificate tables not editable.
> +         * @param data the data of the table
> +         * @param columnNames the names of the columns
> +         * @see DefaultTableModel#DefaultTableModel(Object[][],Object[])
> +         */
> +        public NonEditableTableModel(final Object[][] data, final Object[] columnNames) {
> +            super(data, columnNames);
> +        }
> +
> +        /**
> +         * Constructs a {@link javax.swing.table.TableModel} that serves only one
> +         * purpose: make cells of certificate tables not editable.
> +         * @param columnNames {@code array} containing the names of the new columns;
> +         * if this is {@code null} then the model has no columns
> +         * @param rowCount the number of rows the table holds
> +         * @see DefaultTableModel#DefaultTableModel(Object[],int)
> +         */
> +        public NonEditableTableModel(final Object[] columnNames, final int rowCount) {
> +            super(columnNames, rowCount);
> +        }
> +
> +        /**
> +         * Constructs a {@link javax.swing.table.TableModel} that serves only one
> +         * purpose: make cells of certificate tables not editable.
> +         * @param columnNames {@code vector} containing the names of the new columns;
> +         * if this is {@code null} then the model has no columns
> +         * @param rowCount the number of rows the table holds
> +         * @see DefaultTableModel#DefaultTableModel(Vector,int)
> +         */
> +        public NonEditableTableModel(final Vector columnNames, final int rowCount) {
> +            super(columnNames, rowCount);
> +        }
> +
> +        /**
> +         * Constructs a {@link javax.swing.table.TableModel} that serves only one
> +         * purpose: make cells of certificate tables not editable.
> +         * @param data the data of the table, a {@code Vector} of {@code Vector}s
> +         * of {@code Object} values
> +         * @param columnNames {@code vector} containing the names of the new columns
> +         * @see DefaultTableModel#DefaultTableModel(Vector,Vector)
> +         */
> +        public NonEditableTableModel(final Vector data, final Vector columnNames) {
> +            super(data, columnNames);
> +        }
> +
> +        /**
> +         * This method always returns {@code false} to make the table's cells not
> +         * editable.
> +         * @param row the row whose value to be queried
> +         * @param column the column whose value to be queried
> +         * @return always {@code false}
> +         */
> +        @Override
> +        public boolean isCellEditable(final int row, final int column) {
> +            return false;
> +        }
> +    }
> +}
> \ No newline at end of file
> 


Again - greate work! Thank you vcery much for it

J.



More information about the distro-pkg-dev mailing list