com.rubenlaguna.en4j.mainmodule.NoteListTopComponent.java Source code

Java tutorial

Introduction

Here is the source code for com.rubenlaguna.en4j.mainmodule.NoteListTopComponent.java

Source

/*
 *  Copyright (C) 2010 Ruben Laguna <ruben.laguna@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.rubenlaguna.en4j.mainmodule;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.EventSelectionModel;
import ca.odell.glazedlists.swing.EventTableModel;
import ca.odell.glazedlists.swing.TableComparatorChooser;
import ca.odell.renderpack.DateTableCellRenderer;
import java.beans.PropertyChangeEvent;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLayeredPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableModel;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
//import org.openide.util.ImageUtilities;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.util.Lookup;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import com.rubenlaguna.en4j.interfaces.NoteFinder;
import com.rubenlaguna.en4j.interfaces.NoteRepository;
import com.rubenlaguna.en4j.noteinterface.Note;
import java.beans.PropertyChangeListener;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.OverlayLayout;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentListener;
import javax.swing.table.TableCellRenderer;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.openide.util.RequestProcessor;

/**
 * Top component which displays something.
 */
@ConvertAsProperties(dtd = "-//com.rubenlaguna.en4j.mainmodule//NoteList//EN", autostore = false)
public final class NoteListTopComponent extends TopComponent
        implements ListSelectionListener, PropertyChangeListener {

    public static final int ALLNOTESREFRESHDELAY = 4000;
    public static final int DELAY = 1000;
    private static final Logger LOG = Logger.getLogger(NoteListTopComponent.class.getName());
    private static final RequestProcessor RP = new RequestProcessor("search tasks", 1);
    private static final RequestProcessor RPALLNOTESUPDATE = new RequestProcessor("all notes update", 1);
    private static NoteListTopComponent instance;
    /** path to the icon used by the component and its open action */
    //    static final String ICON_PATH = "SET/PATH/TO/ICON/HERE";
    private static final String PREFERRED_ID = "NoteListTopComponent";
    private final InstanceContent ic = new InstanceContent();
    private RequestProcessor.Task currentSearchTask = null;
    private RepeatableTask updateAllNotesTask = null;
    private RequestProcessor.Task undimTask = null;
    private RepeatableTask refreshTask = null;
    private final AtomicInteger dimCounter = new AtomicInteger(0);
    private String searchstring = "";
    private final CustomGlassPane customGlassPane = new CustomGlassPane();
    //empty it will be populated in componentOpened
    private EventList<Note> allNotes = GlazedLists.threadSafeList(new BasicEventList<Note>());
    private final NoteMatcherEditor notesMatcher = new NoteMatcherEditor();
    private FilterList<Note> filteredList = new FilterList<Note>(allNotes, notesMatcher);
    private SortedList<Note> sortedList = new SortedList<Note>(filteredList);
    private EventSelectionModel selectionModel = null;
    private long lastPropertyChangeTimestamp = 0;

    public NoteListTopComponent() {
        LOG.log(Level.INFO, "creating NoteListTopComponen {0}", this.toString());
        initComponents();
        setName(NbBundle.getMessage(NoteListTopComponent.class, "CTL_NoteListTopComponent"));
        setToolTipText(NbBundle.getMessage(NoteListTopComponent.class, "HINT_NoteListTopComponent"));
        associateLookup(new AbstractLookup(ic));
        putClientProperty(PROP_CLOSING_DISABLED, true);
        //        jTable1.getSelectionModel().addListSelectionListener(this);
        customGlassPane.setVisible(false);
        jLayeredPane1.add(customGlassPane, (Integer) (JLayeredPane.DEFAULT_LAYER + 50));
    }

    public void refresh() {
        LOG.log(Level.FINE, "refresh notelist {0}. just performSearch again",
                new SimpleDateFormat("h:mm:ss a").format(new Date()));
        performSearch(false);
    }

    private RepeatableTask createUpdateAllNotesTask() {
        Runnable runnable = new Runnable() {

            @Override
            @SuppressWarnings(value = "SleepWhileHoldingLock")
            public void run() {
                updateAllNotes();
                NoteListTopComponent.this.refresh();
            }

            private void updateAllNotes() {
                final Collection<Note> allNotesInDb = getAllNotesInDb();
                LOG.info("clear and repopulate allNotes list");

                final Collection<Note> toRemove = CollectionUtils.subtract(allNotes, allNotesInDb);
                final Collection<Note> toAdd = CollectionUtils.subtract(allNotesInDb, allNotes);

                long startLockList = System.currentTimeMillis();
                //allNotes.getReadWriteLock().writeLock().lock();
                try {
                    // avoid removeAll as it would block allNotes for too long
                    CollectionUtils.forAllDo(toRemove, new Closure() {
                        @Override
                        @SuppressWarnings("element-type-mismatch")
                        public void execute(Object input) {
                            allNotes.remove(input);
                        }
                    });
                    //avoid allNotes.addAll it could block allNotes for too long
                    CollectionUtils.addAll(allNotes, toAdd.iterator());
                } finally {
                    //allNotes.getReadWriteLock().writeLock().unlock();
                }
                long deltaListLock = System.currentTimeMillis() - startLockList;
                LOG.log(Level.INFO, "We locked the eventlist for {0} ms", deltaListLock);
                LOG.log(Level.INFO, "allNotes size: {0}  allNotesInDb size: {1}",
                        new Object[] { allNotes.size(), allNotesInDb.size() });
            }
        };
        return new RepeatableTask(runnable, 4000);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLayeredPane1 = new javax.swing.JLayeredPane();
        jLayeredPane1.setLayout(new OverlayLayout(jLayeredPane1));
        jScrollPane1 = new javax.swing.JScrollPane();
        jTable1 = new javax.swing.JTable();
        searchJButton = new javax.swing.JButton();
        partialResultsJLabel = new javax.swing.JLabel();

        jScrollPane1.setBounds(jLayeredPane1.getVisibleRect());

        jTable1.setModel(getGlazedListTableModel());
        jTable1.setBounds(jScrollPane1.getVisibleRect());
        jTable1.setColumnSelectionAllowed(true);
        jScrollPane1.setViewportView(jTable1);
        jTable1.getColumnModel().getSelectionModel()
                .setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);

        jScrollPane1.setBounds(0, 0, 450, -1);
        jLayeredPane1.add(jScrollPane1, javax.swing.JLayeredPane.DEFAULT_LAYER);

        searchTextField.setText(org.openide.util.NbBundle.getMessage(NoteListTopComponent.class,
                "NoteListTopComponent.searchTextField.text")); // NOI18N
        searchTextField.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusGained(java.awt.event.FocusEvent evt) {
                searchTextFieldFocusGained(evt);
            }

            public void focusLost(java.awt.event.FocusEvent evt) {
                searchTextFieldFocusLost(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(searchJButton, org.openide.util.NbBundle
                .getMessage(NoteListTopComponent.class, "NoteListTopComponent.searchJButton.text")); // NOI18N
        searchJButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                searchJButtonActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(partialResultsJLabel, org.openide.util.NbBundle
                .getMessage(NoteListTopComponent.class, "NoteListTopComponent.partialResultsJLabel.text")); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(
                javax.swing.GroupLayout.Alignment.TRAILING,
                layout.createSequentialGroup().addContainerGap()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                .addComponent(jLayeredPane1, javax.swing.GroupLayout.Alignment.LEADING,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, 469, Short.MAX_VALUE)
                                .addGroup(layout.createSequentialGroup()
                                        .addComponent(searchTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 304,
                                                Short.MAX_VALUE)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(partialResultsJLabel)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(searchJButton)))
                        .addContainerGap()));
        layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup().addGap(30, 30, 30).addGroup(layout
                        .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(searchTextField, javax.swing.GroupLayout.PREFERRED_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addComponent(searchJButton).addComponent(partialResultsJLabel,
                                javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addGap(10, 10, 10)
                        .addComponent(jLayeredPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 286, Short.MAX_VALUE)
                        .addContainerGap()));
    }// </editor-fold>//GEN-END:initComponents

    private void searchJButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchJButtonActionPerformed
        performSearch(true);
    }//GEN-LAST:event_searchJButtonActionPerformed

    private void performSearch(final boolean useDim) {

        final RequestProcessor.Task previousSearchTask = currentSearchTask;

        if (previousSearchTask != null) {
            previousSearchTask.cancel();
        }

        Runnable r = new Runnable() {

            @Override
            public void run() {
                if (previousSearchTask != null) {
                    previousSearchTask.waitFinished();
                }
                LOG.log(Level.FINE, "{0} searchstring {1}", new Object[] { this.toString(), searchstring });

                dim(useDim);

                final String text = searchstring;
                LOG.fine("searching in lucene...");
                if (text.trim().isEmpty() || text.equals(org.openide.util.NbBundle
                        .getMessage(NoteListTopComponent.class, "NoteListTopComponent.searchTextField.text"))) {
                    LOG.log(Level.FINE, "no need to search the search box is empty {0} from thread {1}",
                            new Object[] { text, Thread.currentThread().getName() });
                    notesMatcher.refilter(null);
                } else {
                    NoteFinder finder = Lookup.getDefault().lookup(NoteFinder.class);
                    Collection<Note> prelList = finder.find(text);
                    LOG.log(Level.FINE, "search for {0} returned {1} results.",
                            new Object[] { text, prelList.size() });
                    notesMatcher.refilter(prelList);
                }
                final int repSize = Lookup.getDefault().lookup(NoteRepository.class).size();
                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        final String text = filteredList.size() + "/" + repSize;
                        LOG.log(Level.FINE, "Refreshing the label in the EDT with {0}", text);
                        partialResultsJLabel.setText(text);
                    }
                });

                unDim(useDim);

            }
        };

        currentSearchTask = RP.post(r, 500);
        LOG.fine("currentSearchtask posted");
    }

    private void searchTextFieldFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_searchTextFieldFocusGained
        // TODO add your handling code here:
        String initialText = org.openide.util.NbBundle.getMessage(NoteListTopComponent.class,
                "NoteListTopComponent.searchTextField.text");
        if (initialText.equals(searchTextField.getText())) {
            searchTextField.setText("");
        }
    }//GEN-LAST:event_searchTextFieldFocusGained

    private void searchTextFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_searchTextFieldFocusLost
        // TODO add your handling code here:
        if ("".equals(searchTextField.getText())) {
            LOG.info("searchTextField was empty so reset to the default text");
            searchTextField.setText(org.openide.util.NbBundle.getMessage(NoteListTopComponent.class,
                    "NoteListTopComponent.searchTextField.text"));
        }
    }//GEN-LAST:event_searchTextFieldFocusLost
     // Variables declaration - do not modify//GEN-BEGIN:variables

    private javax.swing.JLayeredPane jLayeredPane1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTable1;
    private javax.swing.JLabel partialResultsJLabel;
    private javax.swing.JButton searchJButton;
    public final javax.swing.JTextField searchTextField = new javax.swing.JTextField();
    // End of variables declaration//GEN-END:variables

    /**
     * Gets default instance. Do not use directly: reserved for *.settings files only,
     * i.e. deserialization routines; otherwise you could get a non-deserialized instance.
     * To obtain the singleton instance, use {@link #findInstance}.
     */
    public static synchronized NoteListTopComponent getDefault() {
        if (instance == null) {
            instance = new NoteListTopComponent();
        }

        return instance;
    }

    /**
     * Obtain the NoteListTopComponent instance. Never call {@link #getDefault} directly!
     */
    public static synchronized NoteListTopComponent findInstance() {
        TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
        if (win == null) {
            Logger.getLogger(NoteListTopComponent.class.getName()).warning("Cannot find " + PREFERRED_ID
                    + " component. It will not be located properly in the window system.");
            return getDefault();
        }
        if (win instanceof NoteListTopComponent) {
            return (NoteListTopComponent) win;
        }
        Logger.getLogger(NoteListTopComponent.class.getName())
                .warning("There seem to be multiple components with the '" + PREFERRED_ID
                        + "' ID. That is a potential source of errors and unexpected behavior.");
        return getDefault();
    }

    @Override
    public int getPersistenceType() {
        return TopComponent.PERSISTENCE_NEVER;
    }

    @Override
    public void componentOpened() {
        allNotes.addAll(getAllNotesInDb());
        selectionModel = new EventSelectionModel(sortedList);
        jTable1.setSelectionModel(selectionModel);
        jTable1.getSelectionModel().addListSelectionListener(this);

        TableCellRenderer dateRenderer = new DateTableCellRenderer("yyyy-MM-dd HH:mm,E");
        jTable1.getColumnModel().getColumn(1).setCellRenderer(dateRenderer);
        jTable1.getColumnModel().getColumn(2).setCellRenderer(dateRenderer);

        TableComparatorChooser tableSorter = TableComparatorChooser.install(jTable1, sortedList,
                TableComparatorChooser.SINGLE_COLUMN);

        NoteRepository rep = Lookup.getDefault().lookup(NoteRepository.class);
        rep.addPropertyChangeListener(this);
        Lookup.getDefault().lookup(NoteFinder.class).addPropertyChangeListener(this);
        LOG.log(Level.INFO, "{0} registered as listener to NoteRepositor and NoteFinder", this.toString());
        searchTextField.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                search();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                search();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                search();
            }

            private void search() {
                searchstring = searchTextField.getText();
                LOG.log(Level.INFO, "searchTextField changed: {0}", searchTextField.getText());
                performSearch(true);
            }
        });
        performSearch(false); // to populate the table
    }

    @Override
    public void componentClosed() {
        // TODO add custom code on component closing
        NoteRepository rep = Lookup.getDefault().lookup(NoteRepository.class);
        rep.removePropertyChangeListener(this);
        Lookup.getDefault().lookup(NoteFinder.class).removePropertyChangeListener(this);
        LOG.log(Level.INFO, "{0} removed as listener to NoteRepositor and NoteFinder", this.toString());
    }

    @Override
    protected void componentActivated() {
        searchTextField.requestFocusInWindow();
    }

    void writeProperties(java.util.Properties p) {
        // better to version settings since initial version as advocated at
        // http://wiki.apidesign.org/wiki/PropertyFiles
        p.setProperty("version", "1.0");
        // TODO store your settings
    }

    Object readProperties(java.util.Properties p) {
        NoteListTopComponent singleton = NoteListTopComponent.getDefault();
        singleton.readPropertiesImpl(p);
        return singleton;
    }

    private void readPropertiesImpl(java.util.Properties p) {
        String version = p.getProperty("version");
        // TODO read your settings according to their version
    }

    @Override
    protected String preferredID() {
        return PREFERRED_ID;
    }

    private Collection<Note> getAllNotesInDb() {
        NoteRepository rep = Lookup.getDefault().lookup(NoteRepository.class);
        return rep.getAllNotes();
    }

    @Override
    /**
     * @see         javax.swing.event.ListSelectionListener#valueChanged
     */
    public void valueChanged(ListSelectionEvent arg0) {
        if (!arg0.getValueIsAdjusting()) {
            if (selectionModel != null) {
                EventList<Note> selectionList = selectionModel.getSelected();
                if ((selectionList != null) && (!selectionList.isEmpty())) {
                    Object value = selectionList.get(0);
                    if (value != null) {
                        LOG.log(Level.FINE, "selection changed: {0}", value.toString());
                        ic.set(Collections.singleton(value), null);
                        return;
                    }
                }
            }
            ic.set(Collections.emptySet(), null);
            LOG.log(Level.FINE, "selection changed: nothing selected");
        }
    }

    public synchronized void unDim(boolean reallyUnDim) {
        if (!reallyUnDim) {
            return;
        }
        final int valAfterDecrement = dimCounter.decrementAndGet();

        if (valAfterDecrement < 1) {

            if (undimTask != null) {
                undimTask.schedule(500);
            } else {
                Runnable runnable = new Runnable() {

                    public void run() {
                        if (dimCounter.get() < 1) {
                            setGlasspane(false);
                        }
                    }
                };
                undimTask = RP.post(runnable, 500);
            }
        }
        LOG.log(Level.INFO, "undimmed: {0}", valAfterDecrement);

        while (dimCounter.get() < 0) {
            // if dimcounter when under zero, something wrong
            // try to reset it to zero
            final int cur = dimCounter.get();
            if (cur < 0) {
                LOG.log(Level.SEVERE, "dimCounter is less that zero  ({0})", cur);
                dimCounter.compareAndSet(cur, 0);
            }
        }
    }

    public synchronized void dim(boolean reallyDim) {
        if (!reallyDim) {
            return;
        }
        if (dimCounter.get() < 0) {
            throw new IllegalStateException("dimCounter < 0 (" + dimCounter.get() + ")");
        }
        setGlasspane(true);
        final int value = dimCounter.incrementAndGet();
        LOG.log(Level.INFO, "dimmed: {0}", value);
    }

    public void setGlasspane(final boolean visible) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                customGlassPane.setVisible(visible);
            }
        });
    }

    @Override
    public synchronized void propertyChange(final PropertyChangeEvent evt) {
        LOG.info("change in noterepository / index");
        if (updateAllNotesTask == null) {
            updateAllNotesTask = createUpdateAllNotesTask();
        }
        if (refreshTask == null) {
            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    NoteListTopComponent.this.refresh();

                }
            };
            refreshTask = new RepeatableTask(runnable, 4000);
        }

        if ("notes".equals(evt.getPropertyName())) {
            LOG.log(Level.INFO, "Event {0} received  updating allNotes", evt.getPropertyName());
            updateAllNotesTask.schedule();
        } else {
            LOG.log(Level.INFO, "Event {0} doesn't require update of allNotes", evt.getPropertyName());
        }
        refreshTask.schedule();
    }

    private TableModel getGlazedListTableModel() {
        String[] propertyNames = { "title", "created", "updated" };
        String[] columnLabels = { "Title", "Created", "Last modified" };
        TableFormat<Note> tf = GlazedLists.tableFormat(Note.class, propertyNames, columnLabels);
        EventTableModel<Note> etm = new EventTableModel<Note>(sortedList, tf);

        return etm;
    }
}