Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2004 University of New Mexico and the 
4
 *                  Regents of the University of California
5
 *
6
 *   '$Author: costa $'
7
 *     '$Date: 2005-01-14 12:41:20 -0800 (Fri, 14 Jan 2005) $'
8
 * '$Revision: 2375 $'
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation; either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program; if not, write to the Free Software
22
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
 */
24

    
25
package edu.ucsb.nceas.metacat.harvesterClient;
26

    
27
import java.awt.BorderLayout;
28
import java.awt.Dimension;
29
import java.awt.event.ActionEvent;
30
import java.awt.event.ActionListener;
31
import java.io.File;
32
import java.io.FileInputStream;
33
import java.io.FileWriter;
34
import java.io.IOException;
35
import java.io.InputStream;
36
import java.io.InputStreamReader;
37
import java.io.PrintStream;
38
import java.io.PrintWriter;
39
import java.io.Reader;
40
import java.util.Properties;
41
import javax.swing.JButton;
42
import javax.swing.JFileChooser;
43
import javax.swing.JFrame;
44
import javax.swing.JMenu;
45
import javax.swing.JMenuBar;
46
import javax.swing.JMenuItem;
47
import javax.swing.JOptionPane;
48
import javax.swing.JPanel;
49
import javax.swing.JScrollPane;
50
import javax.swing.JTable;
51
import javax.swing.KeyStroke;
52
import javax.swing.ListSelectionModel;
53
import javax.swing.event.ListSelectionEvent;
54
import javax.swing.event.ListSelectionListener;
55
import javax.swing.table.AbstractTableModel;
56
import javax.swing.table.TableColumn;
57
import javax.swing.table.TableModel;
58
import javax.swing.text.Document;
59
import javax.xml.parsers.ParserConfigurationException;
60
import org.xml.sax.Attributes;
61
import org.xml.sax.ContentHandler;
62
import org.xml.sax.ErrorHandler;
63
import org.xml.sax.InputSource;
64
import org.xml.sax.SAXException;
65
import org.xml.sax.SAXParseException;
66
import org.xml.sax.XMLReader;
67
import org.xml.sax.helpers.DefaultHandler;
68
import org.xml.sax.helpers.XMLReaderFactory;
69

    
70
/**
71
 * The Harvest List Editor reads a Harvest List XML file and displays it as a 
72
 * JTable. Allows user to add, modify, or delete <Document> elements in the
73
 * Harvest List, and save changes back to the disk.
74
 * 
75
 */
76
public class HarvestListEditor extends JFrame implements ActionListener {
77

    
78
  String clipboardDocumentType = "";
79
  String clipboardDocumentURL = "";
80
  String clipboardIdentifier = "";
81
  String clipboardRevision = "";
82
  String clipboardScope = "";
83
  JButton copyButton;
84
  JButton cutButton;
85
  String defaultDocumentType = "eml://ecoinformatics.org/eml-2.0.1";
86
  String defaultDocumentURL = "http://";
87
  String defaultHarvestList = "";
88
  String defaultIdentifier = "1";
89
  String defaultRevision = "1";
90
  String defaultScope = "dataset";
91
  Document doc;
92
  JScrollPane docPane;
93
  JFileChooser fileChooser = new JFileChooser();
94
  File harvestListFile;
95
  boolean harvestListHasChanged = false;
96
  JMenuBar menuBar;
97
  final int numColumns = 6;
98
  final int numRows = 1200;
99
  JButton pasteButton;
100
  JButton pasteDefaultsButton;
101
  Properties properties;
102
  private String schemaLocation = 
103
    "eml://ecoinformatics.org/harvestList harvestList.xsd";
104
  int selectedRow = -1;
105
  final JTable table;
106
  TableModel tableModel;
107
  File tempFile;
108
  String title = "Harvest List Editor";
109
  
110
  // Menu items
111
  JMenuItem exitMenuItem = new JMenuItem("Exit");
112
  JMenuItem newMenuItem = new JMenuItem("New");
113
  JMenuItem openMenuItem = new JMenuItem("Open...");
114
  JMenuItem saveMenuItem = new JMenuItem("Save");
115
  JMenuItem saveAsMenuItem = new JMenuItem("Save As...");
116
  JMenuItem validateMenuItem = new JMenuItem("Validate");
117
  
118

    
119
  /**
120
   * The main program. Instantiate the main frame, a HarvestListEditor object.
121
   * 
122
   * @param args
123
   */
124
  public static void main(String[] args) {
125
    HarvestListEditor harvestListEditor = new HarvestListEditor();
126
  }
127

    
128

    
129
  /**
130
   * Constructor for HarvestListEditor class.
131
   */
132
  public HarvestListEditor() {
133
    super("Harvest List Editor");
134

    
135
    JPanel buttonPanel = new JPanel();
136
    String[] fileItems = 
137
                  new String[] {"New", "Open...", "Save", "Save As...", "Exit"};
138
    JMenu fileMenu = new JMenu("File");
139
    char[] fileShortCuts = {'N', 'O', 'S', 'A', 'X'};
140
    TableColumn tableColumn;
141
    
142
    loadProperties();
143
    setSize(1000, 400);
144
    setDefaultCloseOperation(EXIT_ON_CLOSE);
145
    menuBar = new JMenuBar();
146
    
147
    /*
148
     * Add menu items to the File menu
149
     */
150
    newMenuItem.setAccelerator(KeyStroke.getKeyStroke('N',
151
									                  java.awt.Event.CTRL_MASK,
152
                                                      false));
153
    newMenuItem.addActionListener(this);
154
    fileMenu.add(newMenuItem);
155

    
156
    openMenuItem.setAccelerator(KeyStroke.getKeyStroke('O',
157
									                   java.awt.Event.CTRL_MASK,
158
                                                       false));
159
    openMenuItem.addActionListener(this);
160
    fileMenu.add(openMenuItem);
161

    
162
    saveMenuItem.setAccelerator(KeyStroke.getKeyStroke('S',
163
									                   java.awt.Event.CTRL_MASK,
164
                                                       false));
165
    saveMenuItem.addActionListener(this);
166
    saveMenuItem.setEnabled(false);
167
    fileMenu.add(saveMenuItem);
168

    
169
    saveAsMenuItem.setAccelerator(KeyStroke.getKeyStroke('A',
170
									                   java.awt.Event.CTRL_MASK,
171
                                                       false));
172
    saveAsMenuItem.addActionListener(this);
173
    fileMenu.add(saveAsMenuItem);
174

    
175
    validateMenuItem.setAccelerator(KeyStroke.getKeyStroke('V',
176
									                   java.awt.Event.CTRL_MASK,
177
                                                       false));
178
    validateMenuItem.addActionListener(this);
179
    fileMenu.add(validateMenuItem);
180

    
181
    exitMenuItem.setAccelerator(KeyStroke.getKeyStroke('X',
182
									                   java.awt.Event.CTRL_MASK,
183
                                                       false));
184
    exitMenuItem.addActionListener(this);
185
    fileMenu.add(exitMenuItem);
186
    
187
    menuBar.add(fileMenu);      // Add the File menu to the menu bar
188
    setJMenuBar(menuBar);       // Set the frame's menu bar to this menu bar
189

    
190
    //table = new JTable(numRows, numColumns);
191
    table = new JTable(new HarvestListTableModel());
192
    table.setPreferredScrollableViewportSize(new Dimension(900, 300));
193
    tableColumn = table.getColumnModel().getColumn(0);
194
    tableColumn.setPreferredWidth(30);
195
    tableColumn = table.getColumnModel().getColumn(1);
196
    tableColumn.setPreferredWidth(200);
197
    tableColumn = table.getColumnModel().getColumn(2);
198
    tableColumn.setPreferredWidth(50);
199
    tableColumn = table.getColumnModel().getColumn(3);
200
    tableColumn.setPreferredWidth(50);
201
    tableColumn = table.getColumnModel().getColumn(4);
202
    tableColumn.setPreferredWidth(250);
203
    tableColumn = table.getColumnModel().getColumn(5);
204
    tableColumn.setPreferredWidth(320);
205
    
206
    table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
207
    tableModel = table.getModel();
208
    initHarvestList();
209

    
210
    //Ask to be notified of selection changes.
211
    ListSelectionModel rowSM = table.getSelectionModel();
212

    
213
    rowSM.addListSelectionListener(new ListSelectionListener() {
214
      public void valueChanged(ListSelectionEvent e) {
215
        ListSelectionModel lsm;
216

    
217
        //Ignore extra messages.
218
        if (e.getValueIsAdjusting()) return;
219

    
220
        lsm = (ListSelectionModel)e.getSource();
221

    
222
        // If now row is selected, disable all buttons.
223
        if (lsm.isSelectionEmpty()) {
224
          selectedRow = -1;
225
          cutButton.setEnabled(false);
226
          copyButton.setEnabled(false);
227
          pasteButton.setEnabled(false);
228
          pasteDefaultsButton.setEnabled(false);
229
        }
230
        // If a row is selected, manage the buttons based on the selected row
231
        // and the current clipboard values.
232
        else {
233
          selectedRow = lsm.getMinSelectionIndex();
234
          manageButtons(selectedRow);
235
        }
236
      }
237
    });
238

    
239
    docPane = new JScrollPane(table);
240
    getContentPane().add(docPane, BorderLayout.CENTER);
241

    
242
		cutButton = new JButton("Cut");
243
		copyButton = new JButton("Copy");
244
		pasteButton = new JButton("Paste");
245
		pasteDefaultsButton = new JButton("Paste Defaults");
246

    
247
    // Action listener for the Copy button.    
248
    copyButton.addActionListener(new ActionListener() {
249
      public void actionPerformed(ActionEvent ae) {				
250
        copyRow(selectedRow);
251
        manageButtons(selectedRow);
252
        harvestListHasChanged = true;
253
			}
254
		}
255
    );
256

    
257
    // Action listener for the Cut button.    
258
    cutButton.addActionListener(new ActionListener() {
259
      public void actionPerformed(ActionEvent ae) {				
260
        cutRow(selectedRow);
261
        manageButtons(selectedRow);
262
        harvestListHasChanged = true;
263
			}
264
		}
265
    );
266

    
267
    // Action listener for the Paste button.    
268
    pasteButton.addActionListener(new ActionListener() {
269
      public void actionPerformed(ActionEvent ae) {
270
        pasteRow(selectedRow);
271
        manageButtons(selectedRow);
272
        harvestListHasChanged = true;
273
			}
274
		}
275
    );
276

    
277
    // Action listener for the Paste Defaults button.    
278
    pasteDefaultsButton.addActionListener(new ActionListener() {
279
      public void actionPerformed(ActionEvent ae) {
280
        pasteDefaultValues(selectedRow);
281
        manageButtons(selectedRow);
282
        harvestListHasChanged = true;
283
			}
284
		}
285
    );
286

    
287
    cutButton.setEnabled(false);
288
    copyButton.setEnabled(false);
289
    pasteButton.setEnabled(false);
290
    pasteDefaultsButton.setEnabled(false);
291
    buttonPanel.add(cutButton);
292
    buttonPanel.add(copyButton);
293
    buttonPanel.add(pasteButton);
294
    buttonPanel.add(pasteDefaultsButton);
295
    buttonPanel.setOpaque(true);
296
    getContentPane().add(buttonPanel, BorderLayout.SOUTH);
297

    
298
    // If the default Harvest List option has a value, and the file exists, 
299
    // loads its contents.
300
    //
301
    if ((defaultHarvestList != null) && (!defaultHarvestList.equals(""))) {
302
      harvestListFile = new File(defaultHarvestList);
303
      if (harvestListFile.exists()) {
304
        try {
305
          loadHarvestList(harvestListFile);
306
          saveMenuItem.setEnabled(true);
307
        }
308
        catch (ParserConfigurationException e) {
309
          System.out.println("Error parsing Harvest List: " + e.getMessage());
310
        }        
311
      }
312
      else {
313
        System.out.println(
314
          "Warning: the default harvest list file that was specified in the " + 
315
          ".harvestListEditor configuration file does not exist:\n" +
316
          harvestListFile
317
                          );
318
        fileNew();
319
      }
320
    }
321
    else {
322
      fileNew();
323
    }
324

    
325
    try {    
326
      tempFile = File.createTempFile("harvestListTemp", ".xml");
327
    }
328
    catch (IOException ioe) {
329
      System.out.println("Error creating temporary file: " + ioe.getMessage());
330
    }
331

    
332
		setVisible(true);
333
  }
334

    
335

    
336
  /**
337
   * Implements action listeners for menu items.
338
   * 
339
   * @param e   An ActionEvent object, determines the menu item that was
340
   *            selected.
341
   */
342
  public void actionPerformed(ActionEvent e) {
343
    if ((e.getActionCommand()).equals("New")) {
344
      fileNew();
345
    }    
346
    else if ((e.getActionCommand()).equals("Open...")) {
347
      fileOpen();
348
    }    
349
    else if ((e.getActionCommand()).equals("Save")) {
350
      fileSave();
351
    }    
352
    else if ((e.getActionCommand()).equals("Save As...")) {
353
      fileSaveAs();
354
    }
355
    else if ((e.getActionCommand()).equals("Validate")) {
356
      fileValidate();
357
    }
358
    else if ((e.getActionCommand()).equals("Exit")) {
359
      fileExit();
360
    }
361
  }
362
  
363

    
364
  /**
365
   * Adds a new row to the table, setting values for each of the five columns
366
   * in the row.
367
   * 
368
   * @param rowIndex              the row index
369
   * @param scope                 the scope string
370
   * @param identifier            the identifier string
371
   * @param revision              the revision string
372
   * @param documentType          the document type
373
   * @param documentURL           the document URL
374
   */
375
  void addRow(int rowIndex, String scope, String identifier, String revision,
376
              String documentType, String documentURL) {
377
    tableModel.setValueAt(scope,                 rowIndex, 1);
378
    tableModel.setValueAt(identifier,            rowIndex, 2);
379
    tableModel.setValueAt(revision,              rowIndex, 3);
380
    tableModel.setValueAt(documentType,          rowIndex, 4);
381
    tableModel.setValueAt(documentURL,           rowIndex, 5);
382
  }
383

    
384

    
385
  /**
386
   * Composes a single tag line to be written in the Harvest List.
387
   * 
388
   * @param indentLevel    the number of spaces to begin the line with
389
   * @param tag            the tag name
390
   * @param text           the text to insert within the begin and end tags
391
   * @return line          the composed line
392
   */
393
  String composeLine(int indentLevel, String tag, String text) {
394
    String line = "";
395
    
396
    for (int i = 0; i < indentLevel; i++) {
397
      line += " ";
398
    }
399
    
400
    line += "<" + tag + ">";
401
    line += text;
402
    line += "</" + tag + ">";
403
    
404
    return line;
405
  }
406
  
407

    
408
  /**
409
   * Clears all rows in the table, setting all values to null.
410
   */
411
  void clearHarvestList() {
412
    for (int rowIndex = 0; rowIndex < numRows; rowIndex++) {
413
      clearRow(rowIndex);
414
    }
415
  }
416
  
417

    
418
  /**
419
   * Clears a single row in the tables, setting all five fields to null.
420
   * 
421
   * @param rowIndex   the index to the table row to be cleared
422
   */
423
  void clearRow(int rowIndex) {
424
    final String nil = "";
425
    
426
    tableModel.setValueAt(nil, rowIndex, 1);
427
    tableModel.setValueAt(nil, rowIndex, 2);
428
    tableModel.setValueAt(nil, rowIndex, 3);
429
    tableModel.setValueAt(nil, rowIndex, 4);
430
    tableModel.setValueAt(nil, rowIndex, 5);    
431
  }
432
  
433

    
434
  /**
435
   * Copies the values in a given table row to the clipboard.
436
   * 
437
   * @param rowIndex  the index of the table row to be copied
438
   */
439
  void copyRow(int rowIndex) {
440
    clipboardScope = (String) tableModel.getValueAt(rowIndex, 1);
441
    clipboardIdentifier = (String) tableModel.getValueAt(rowIndex, 2);
442
    clipboardRevision = (String) tableModel.getValueAt(rowIndex, 3);
443
    clipboardDocumentType = (String) tableModel.getValueAt(rowIndex, 4);    
444
    clipboardDocumentURL = (String) tableModel.getValueAt(rowIndex, 5);
445
  }
446
  
447

    
448
  /**
449
   * Cuts a row from the table. The row is copied to the clipboard and then
450
   * cleared.
451
   * 
452
   * @param rowIndex  the index of the table row to be copied
453
   */
454
  void cutRow(int rowIndex) {
455
    copyRow(rowIndex);
456
    clearRow(rowIndex);
457
  }
458
  
459

    
460
  /**
461
   * Exit from the Harvest List Editor. Prompt to save changes if appropriate.
462
   */
463
  void fileExit() {
464
    int value;
465

    
466
    if (harvestListHasChanged) {
467
      value = saveChangesDialog();
468
      
469
      if (value == JOptionPane.YES_OPTION) {
470
        try {
471
          // Save the changes then exit
472
          //
473
          fileSave();
474
          System.exit(0);
475
        }
476
        catch (Exception exception) {
477
          exception.printStackTrace();
478
        }
479
      }
480
      else if (value == JOptionPane.NO_OPTION) {
481
        // Exit without saving changes
482
        System.exit(0);
483
      } 
484
    }
485
    else {
486
      System.exit(0);
487
    }
488
  }
489
  
490

    
491
  /**
492
   * Replace the current Harvest List with an empty Harvest List. Prompt to save
493
   * changes if appropriate.
494
   */
495
  void fileNew() {
496
    int value;
497
    
498
    if (harvestListHasChanged) {
499
      value = saveChangesDialog();
500
      
501
      if (value == JOptionPane.YES_OPTION) {
502
        try {
503
          fileSave();
504
        }
505
        catch (Exception exception) {
506
          exception.printStackTrace();
507
        }
508
      }
509
      else if (value == JOptionPane.CANCEL_OPTION) {
510
        return;
511
      }
512
    }
513

    
514
    clearHarvestList();
515
    harvestListFile = null;
516
    setTitle(title + ": (Untitled)");
517
    saveMenuItem.setEnabled(false);
518
    harvestListHasChanged = false;    
519
  }
520
  
521

    
522
  /**
523
   * Opens a file dialog to load a Harvest List. Prompts to save changes to the
524
   * current Harvest List if appropriate.
525
   */
526
  void fileOpen() {
527
    int value;
528
    
529
    if (harvestListHasChanged) {
530
      value = saveChangesDialog();
531
      
532
      if (value == JOptionPane.YES_OPTION) {
533
        try {
534
          fileSave();
535
        }
536
        catch (Exception exception) {
537
          exception.printStackTrace();
538
        }
539
      }
540
      else if (value == JOptionPane.CANCEL_OPTION) {
541
        return;
542
      }
543
    }
544

    
545
    value = fileChooser.showOpenDialog(HarvestListEditor.this);
546
    
547
    if (value == JFileChooser.APPROVE_OPTION) {
548
      harvestListFile = fileChooser.getSelectedFile();
549
      try {
550
        clearHarvestList();
551
        loadHarvestList(harvestListFile);
552
      }
553
      catch (ParserConfigurationException e) {
554
        System.out.println("Error parsing Harvest List: " + e.getMessage());
555
      }        
556
      harvestListHasChanged = false;
557
      saveMenuItem.setEnabled(true);
558
    }
559
  }
560
  
561

    
562
  /**
563
   * Save the current Harvest List to disk.
564
   */
565
  void fileSave() {
566
    if (harvestListFile != null) {
567
      writeFile(harvestListFile);
568
      harvestListHasChanged = false;
569
    }
570
    else {
571
      System.out.println("No action taken");
572
    }
573
  }
574
  
575

    
576
  /**
577
   * Save the current Harvest List as a potentially different file name.
578
   */
579
  void fileSaveAs() {
580
    int returnVal;
581
    
582
    returnVal = fileChooser.showOpenDialog(HarvestListEditor.this);
583
    
584
    if (returnVal == JFileChooser.APPROVE_OPTION) {
585
      harvestListFile = fileChooser.getSelectedFile();
586
      writeFile(harvestListFile);
587
      setTitle(title + ": " + harvestListFile.getName());
588
      saveMenuItem.setEnabled(true);
589
      harvestListHasChanged = false;
590
    }
591
  }
592
  
593
  
594
  /**
595
   * Validate the Harvest List that is currently stored in the table. This is
596
   * implemented by writing the Harvest List to a temporary file and then
597
   * running the SAX parser on the temporary file.
598
   */
599
  void fileValidate() {
600
    FileInputStream fis;
601
    HarvestListHandler harvestListHandler = new HarvestListHandler();
602
    InputStreamReader inputStreamReader;
603
    boolean loadHarvestList = false;
604
    boolean validateHarvestList = true;
605
    
606
    writeFile(tempFile);
607
    
608
    try {
609
      fis = new FileInputStream(tempFile);
610
      inputStreamReader = new InputStreamReader(fis);
611
      harvestListHandler.runParser(this, inputStreamReader, schemaLocation,
612
                                   loadHarvestList, validateHarvestList);
613
      fis.close();
614
      tempFile.delete();
615
      harvestListMessage("Harvest List is valid");
616
    }
617
    catch (SAXException e) {
618
      harvestListMessage("Validation error: " + e.getMessage());
619
    }
620
    catch (ClassNotFoundException e) {
621
      System.out.println("ClassNotFoundException: " + e.getMessage());
622
    }
623
    catch (IOException ioe) {
624
      System.out.println("Error opening file: " + ioe.getMessage());
625
    }
626
  }
627
  
628

    
629
  /**
630
   * Displays a short message in a dialog box.
631
   * 
632
   * @param message       the message text
633
   */
634
  void harvestListMessage(String message) {
635
    JOptionPane.showMessageDialog(this, message);
636
  }
637
  
638

    
639
  /**
640
   * Initializes the Harvest List table, filling column 0 with row numbers.
641
   * This is a non-editable column, so its values should never change after
642
   * this point.
643
   */
644
  void initHarvestList() {
645
    for (int rowIndex = 0; rowIndex < numRows; rowIndex++) {
646
      tableModel.setValueAt(new Integer(rowIndex + 1).toString(), rowIndex, 0);
647
    }
648
  }
649
  
650

    
651
  /**
652
   * Determines whether the clipboard is currently empty. The clipboard is
653
   * empty if all five of the fields are empty.
654
   * 
655
   * @return      true if the clipboard is empty, else false
656
   */
657
  boolean isEmptyClipboard() {
658
    boolean isEmpty = true;
659
    
660
    isEmpty = isEmpty && (clipboardScope.equals(""));
661
    isEmpty = isEmpty && (clipboardIdentifier.equals(""));
662
    isEmpty = isEmpty && (clipboardRevision.equals(""));
663
    isEmpty = isEmpty && (clipboardDocumentType.equals(""));
664
    isEmpty = isEmpty && (clipboardDocumentURL.equals(""));
665
    
666
    return isEmpty;
667
  }
668
    
669

    
670
  /**
671
   * Determines whether a given row in the table is empty. A row is empty if
672
   * all five of its fields contain either null or "".
673
   * 
674
   * @param rowIndex    the index to the row in the table that is being checked
675
   * @return            true if the row is empty, else false
676
   */
677
  boolean isEmptyRow(int rowIndex) {
678
    boolean isEmpty = true;
679
    String scope = (String) tableModel.getValueAt(rowIndex, 1);
680
    String identifier = (String) tableModel.getValueAt(rowIndex, 2);
681
    String revision = (String) tableModel.getValueAt(rowIndex, 3);
682
    String documentType = (String) tableModel.getValueAt(rowIndex, 4);    
683
    String documentURL = (String) tableModel.getValueAt(rowIndex, 5);
684
    
685
    isEmpty = isEmpty && ((scope == null) || (scope.equals("")));
686
    isEmpty = isEmpty && ((identifier == null) || (identifier.equals("")));
687
    isEmpty = isEmpty && ((revision == null) || (revision.equals("")));
688
    isEmpty = isEmpty && ((documentType == null) || (documentType.equals("")));
689
    isEmpty = isEmpty && ((documentURL == null) || (documentURL.equals("")));
690
    
691
    return isEmpty;
692
  }
693
  
694

    
695
  /**
696
   * Loads the Harvest List from a file. Parses the file using the inner class,
697
   * HarvestListHandler, a SAX parser.
698
   * 
699
   * @param harvestList  the File to be loaded
700
   * @throws ParserConfigurationException
701
   */
702
  void loadHarvestList(File harvestList) throws ParserConfigurationException {
703
    HarvestListHandler harvestListHandler = new HarvestListHandler();
704
    FileInputStream fis;
705
    InputStreamReader inputStreamReader;
706
    boolean loadHarvestList = true;
707
    boolean validateHarvestList = false;
708

    
709
    try {
710
      fis = new FileInputStream(harvestList);
711
      inputStreamReader = new InputStreamReader(fis);
712
      //System.out.println("Opened file successfully.");
713
      harvestListHandler.runParser(this, inputStreamReader, schemaLocation,
714
                                   loadHarvestList, validateHarvestList);
715
      fis.close();
716
      setTitle(title + ": " + harvestListFile.getName());
717
    }
718
    catch (SAXException e) {
719
      System.out.println("Error parsing Harvest List: " + e.getMessage());
720
    }
721
    catch (ClassNotFoundException e) {
722
      System.out.println("ClassNotFoundException: " + e.getMessage());
723
    }
724
    catch (IOException ioe) {
725
      System.out.println("Error opening file: " + ioe.getMessage());
726
    }
727
  }
728
  
729

    
730
  /**
731
   * Loads properties from the .harvestListEditor file in the user's home
732
   * directory.
733
   */
734
  void loadProperties () {
735
    String homedir = System.getProperty("user.home");
736
    File propertiesFile = new File(homedir, ".harvestListEditor");
737

    
738
    properties = new Properties();
739

    
740
    if (propertiesFile.exists()) {
741
      try {
742
        properties.load(new FileInputStream(propertiesFile));
743
        defaultHarvestList = properties.getProperty("defaultHarvestList");
744
        defaultDocumentType = properties.getProperty("defaultDocumentType");
745
        defaultDocumentURL = properties.getProperty("defaultDocumentURL");
746
        defaultIdentifier = properties.getProperty("defaultIdentifier");
747
        defaultRevision = properties.getProperty("defaultRevision");
748
        defaultScope = properties.getProperty("defaultScope");    
749
      }
750
      catch (IOException ioe) {
751
        System.out.println("Error loading properties file: " + 
752
                           ioe.getMessage());
753
      }  
754
    }
755
  }
756
  
757

    
758
  /**
759
   * Enables or disables buttons depending on the state of the currently
760
   * selected row and the state of the clipboard.
761
   * 
762
   * @param rowIndex       the index of the currently selected row
763
   */
764
  void manageButtons(int rowIndex) {
765
    
766
    if (isEmptyRow(rowIndex)) {
767
      // Selected row is empty, so disable cut and copy
768
      cutButton.setEnabled(false);
769
      copyButton.setEnabled(false);
770
    }
771
    else {
772
      // Selected row is not empty, so enable cut and copy
773
      cutButton.setEnabled(true);
774
      copyButton.setEnabled(true);
775
    }
776

    
777
    if (isEmptyClipboard()) {
778
      // Clipboard is empty, so disable paste
779
      pasteButton.setEnabled(false);
780
    }
781
    else {
782
      // Clipboard is not empty, so enable paste
783
      pasteButton.setEnabled(true);
784
    }
785

    
786
    // Paste Defaults button is enabled whenever a row is selected    
787
    pasteDefaultsButton.setEnabled(true);
788
  }
789
  
790

    
791
  /**
792
   * Pastes the clipboard values into the specified row.
793
   * 
794
   * @param rowIndex      the index of the row that is being pasted to
795
   */
796
  void pasteRow(int rowIndex) {
797
    tableModel.setValueAt(clipboardScope,        rowIndex, 1);
798
    tableModel.setValueAt(clipboardIdentifier,   rowIndex, 2);
799
    tableModel.setValueAt(clipboardRevision,     rowIndex, 3);
800
    tableModel.setValueAt(clipboardDocumentType, rowIndex, 4);
801
    tableModel.setValueAt(clipboardDocumentURL,  rowIndex, 5);
802
  }
803
  
804

    
805
  /**
806
   * Pastes the default values into the specified row.
807
   * 
808
   * @param rowIndex      the index of the row that is being pasted to
809
   */
810
  void pasteDefaultValues(int rowIndex) {
811
    tableModel.setValueAt(defaultScope,        rowIndex, 1);
812
    tableModel.setValueAt(defaultIdentifier,   rowIndex, 2);
813
    tableModel.setValueAt(defaultRevision,     rowIndex, 3);
814
    tableModel.setValueAt(defaultDocumentType, rowIndex, 4);
815
    tableModel.setValueAt(defaultDocumentURL,  rowIndex, 5);
816
  }
817
  
818

    
819
  /**
820
   * Dialog to determine whether user wants to save changes before proceeding.
821
   * 
822
   * @return   integer value that determines whether the user responded with
823
   *           "Yes", "No", or "Cancel"
824
   */
825
  int saveChangesDialog () {
826
    Object[] options = {"Yes", "No", "Cancel"};
827
    int value;
828
    
829
    value = JOptionPane.showOptionDialog(null,
830
                                         "Save Changes?",
831
                                         "Warning",
832
                                         JOptionPane.DEFAULT_OPTION,
833
                                         JOptionPane.WARNING_MESSAGE,
834
                                         null,
835
                                         options,
836
                                         options[0]); // default option
837
    return value;
838
  }
839
  
840

    
841
  /**
842
   * Writes the contents of the table to file as XML.
843
   * 
844
   * @param harvestList       the File object to write to
845
   */
846
  void writeFile(File harvestList) {
847
    try {
848
      PrintWriter out = new PrintWriter(new FileWriter(harvestList));
849
      writeHarvestList(out);
850
    }
851
    catch (IOException ioe) {
852
      System.out.println("IOException: " + ioe.getMessage());
853
    }
854
  }
855
  
856

    
857
  /**
858
   * Writes the contents of the table to a PrintWriter.
859
   * 
860
   * @param out       the PrintWriter to write the Harvest List to
861
   */
862
  void writeHarvestList(PrintWriter out) {
863
    out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
864
    out.println("");
865
    out.println(
866
       "<hrv:harvestList xmlns:hrv=\"eml://ecoinformatics.org/harvestList\" >");
867

    
868
    for (int i = 0; i < numRows; i++) {
869
      if (!isEmptyRow(i)) {
870
        writeRow(out, i);
871
      }
872
    }
873

    
874
    out.println("");
875
    out.println("</hrv:harvestList>");
876
    out.close();
877
  }
878
  
879

    
880
  /**
881
   * Writes a row of the table to file. A row corresponds to a single
882
   * <Document> element in the Harvest List.
883
   * 
884
   * @param out       the PrintWriter object for the file
885
   * @param rowIndex  the index of the table row that is being written to file
886
   */
887
  void writeRow(PrintWriter out, int rowIndex) {
888
    int indentLevel = 6;
889
    String scope = (String) tableModel.getValueAt(rowIndex, 1);
890
    String identifier = (String) tableModel.getValueAt(rowIndex, 2);
891
    String revision = (String) tableModel.getValueAt(rowIndex, 3);
892
    String documentType = (String) tableModel.getValueAt(rowIndex, 4);    
893
    String documentURL = (String) tableModel.getValueAt(rowIndex, 5);
894

    
895
    out.println("");
896
    out.println("  <document>");
897
    out.println("    <docid>");
898
    out.println(composeLine(indentLevel, "scope", scope));
899
    out.println(composeLine(indentLevel, "identifier", identifier));
900
    out.println(composeLine(indentLevel, "revision", revision));
901
    out.println("    </docid>");
902
    indentLevel = 4;
903
    out.println(composeLine(indentLevel, "documentType", documentType));
904
    out.println(composeLine(indentLevel, "documentURL", documentURL));
905
    out.println("  </document>");
906
  }
907
  
908

    
909
  /*
910
   * Inner class: HarvestListTableModel
911
   */
912
    class HarvestListTableModel extends AbstractTableModel {
913
      final boolean DEBUG = false;
914
      // Column names for the JTable  
915
      private String[] columnNames = {
916
                          "Row #",
917
                          "Scope",
918
                          "Identifier",
919
                          "Revision",
920
                          "Document Type",
921
                          "Document URL"
922
                         };
923

    
924
      private Object[][] data = new Object[numRows][numColumns];
925

    
926
        public int getColumnCount() {
927
            return columnNames.length;
928
        }
929

    
930
        public int getRowCount() {
931
            return data.length;
932
        }
933

    
934
        public String getColumnName(int col) {
935
            return columnNames[col];
936
        }
937

    
938
        public Object getValueAt(int row, int col) {
939
            return data[row][col];
940
        }
941

    
942
        /*
943
         * JTable uses this method to determine the default renderer/
944
         * editor for each cell.  If we didn't implement this method,
945
         * then the last column would contain text ("true"/"false"),
946
         * rather than a check box.
947
         */
948
        public Class getColumnClass(int c) {
949
            return getValueAt(0, c).getClass();
950
        }
951

    
952
        /*
953
         * Don't need to implement this method unless your table's
954
         * editable.
955
         */
956
        public boolean isCellEditable(int row, int col) {
957
            //Note that the data/cell address is constant,
958
            //no matter where the cell appears onscreen.
959
            if (col < 1) {
960
                return false;
961
            } else {
962
                return true;
963
            }
964
        }
965

    
966
        /*
967
         * Don't need to implement this method unless your table's
968
         * data can change.
969
         */
970
        public void setValueAt(Object value, int row, int col) {
971
            if (DEBUG) {
972
                System.out.println("Setting value at " + row + "," + col
973
                                   + " to " + value
974
                                   + " (an instance of "
975
                                   + value.getClass() + ")");
976
            }
977

    
978
            data[row][col] = value;
979
            fireTableCellUpdated(row, col);
980

    
981
            if (DEBUG) {
982
                System.out.println("New value of data:");
983
                printDebugData();
984
            }
985
        }
986

    
987
        private void printDebugData() {
988
            int numRows = getRowCount();
989
            int numCols = getColumnCount();
990

    
991
            for (int i=0; i < numRows; i++) {
992
                System.out.print("    row " + i + ":");
993
                for (int j=0; j < numCols; j++) {
994
                    System.out.print("  " + data[i][j]);
995
                }
996
                System.out.println();
997
            }
998
            System.out.println("--------------------------");
999
        }
1000
    }
1001

    
1002
  /**
1003
   * This inner class extends DefaultHandler. It parses the Harvest List file,
1004
   * writing a new row to the table every time it encounters a </Document>
1005
   * end tag.
1006
   */
1007
  class HarvestListHandler extends DefaultHandler implements ErrorHandler {
1008
  
1009
    public String scope;
1010
    public int identifier;
1011
    public String identifierString;
1012
    public String documentType;
1013
    private HarvestListEditor harvestListEditor;
1014
    boolean loadHarvestList;
1015
    public int revision;
1016
    public String revisionString;
1017
    private int rowIndex = 0;
1018
    public String documentURL;
1019
    private String currentQname;
1020
    public final static String DEFAULT_PARSER = 
1021
           "org.apache.xerces.parsers.SAXParser";
1022
    private boolean schemaValidate = true;
1023
    private boolean validateHarvestList;
1024
	
1025

    
1026
	  /**
1027
     * This method is called for any plain text within an element.
1028
     * It parses the value for any of the following elements:
1029
     * <scope>, <identifier>, <revision>, <documentType>, <documentURL>
1030
     * 
1031
     * @param ch          the character array holding the parsed text
1032
     * @param start       the start index
1033
     * @param length      the text length
1034
     * 
1035
     */
1036
    public void characters (char ch[], int start, int length) {
1037
      String s = new String(ch, start, length);
1038
 
1039
      if (length > 0) {           
1040
        if (currentQname.equals("scope")) {
1041
          scope += s;
1042
        }
1043
        else if (currentQname.equals("identifier")) {
1044
          identifierString += s;
1045
        }
1046
        else if (currentQname.equals("revision")) {
1047
          revisionString += s;
1048
        }
1049
        else if (currentQname.equals("documentType")) {
1050
          documentType += s;
1051
        }
1052
        else if (currentQname.equals("documentURL")) {
1053
          documentURL += s;
1054
        }
1055
      }
1056
    }
1057

    
1058

    
1059
    /** 
1060
     * Handles an end-of-document event.
1061
     */
1062
    public void endDocument () {
1063
    }
1064

    
1065

    
1066
    /** 
1067
     * Handles an end-of-element event. If the end tag is </Document>, then
1068
     * creates a new HarvestDocument object and pushes it to the document
1069
     * list.
1070
     * 
1071
     * @param uri
1072
     * @param localname
1073
     * @param qname
1074
     */
1075
    public void endElement(String uri, 
1076
                           String localname,
1077
                           String qname) {
1078
      
1079
      HarvestDocument harvestDocument;
1080
      
1081
      if (qname.equals("identifier")) {
1082
        identifier = Integer.parseInt(identifierString);
1083
      }
1084
      else if (qname.equals("revision")) {
1085
        revision = Integer.parseInt(revisionString);
1086
      }
1087
      else if (qname.equals("document")) {
1088
        if (loadHarvestList) {
1089
          harvestListEditor.addRow(rowIndex, scope, identifierString, 
1090
                                   revisionString, documentType, documentURL);
1091
        }
1092

    
1093
        rowIndex++;
1094
      }
1095

    
1096
      currentQname = "";
1097
    }
1098

    
1099

    
1100
    /**
1101
     * Method for handling errors during a parse
1102
     *
1103
     * @param exception         The parsing error
1104
     * @exception SAXException  Description of Exception
1105
     */
1106
     public void error(SAXParseException e) throws SAXParseException {
1107
        System.out.println("SAXParseException: " + e.getMessage());
1108
        throw e;
1109
    }
1110

    
1111

    
1112
    /**
1113
     * Run the validating parser
1114
     *
1115
     * @param xml             the xml stream to be validated
1116
     * @schemaLocation        relative path the to XML Schema file, e.g. "."
1117
     * @exception IOException thrown when test files can't be opened
1118
     * @exception ClassNotFoundException thrown when SAX Parser class not found
1119
     * @exception SAXException
1120
     * @exception SAXParserException
1121
     */
1122
    public void runParser(HarvestListEditor harvestListEditor,
1123
                          Reader xml, 
1124
                          String schemaLocation,
1125
                          boolean loadHarvestList,
1126
                          boolean validateHarvestList)
1127
           throws IOException, ClassNotFoundException,
1128
                  SAXException, SAXParseException {
1129

    
1130
      // Get an instance of the parser
1131
      XMLReader parser;
1132
      
1133
      this.harvestListEditor = harvestListEditor;
1134
      this.loadHarvestList = loadHarvestList;
1135
      this.validateHarvestList = validateHarvestList;
1136
      this.rowIndex = 0;
1137

    
1138
      parser = XMLReaderFactory.createXMLReader(DEFAULT_PARSER);
1139
      // Set Handlers in the parser
1140
      parser.setContentHandler((ContentHandler)this);
1141
      parser.setErrorHandler((ErrorHandler)this);
1142
      parser.setFeature("http://xml.org/sax/features/namespaces", true);
1143
      parser.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
1144
      parser.setFeature("http://xml.org/sax/features/validation", true);
1145
      parser.setProperty(
1146
              "http://apache.org/xml/properties/schema/external-schemaLocation", 
1147
              schemaLocation);
1148

    
1149
      if (schemaValidate) {
1150
        parser.setFeature("http://apache.org/xml/features/validation/schema", 
1151
                          true);
1152
      }
1153
    
1154
      // Parse the document
1155
      parser.parse(new InputSource(xml));
1156
    }
1157

    
1158

    
1159
    /**
1160
     * Handles a start-of-document event.
1161
     */
1162
    public void startDocument () {
1163
      //System.out.println("Started parsing Harvest List");
1164
    }
1165

    
1166

    
1167
    /** 
1168
     * Handles a start-of-element event.
1169
     * 
1170
     * @param uri
1171
     * @param localname
1172
     * @param qname
1173
     * @param attributes
1174
     */
1175
    public void startElement(String uri, 
1176
                             String localname,
1177
                             String qname,
1178
                             Attributes attributes) {
1179
      
1180
      currentQname = qname;
1181

    
1182
      if (qname.equals("scope")) {
1183
        scope = "";
1184
      }
1185
      else if (qname.equals("identifier")) {
1186
        identifierString = "";
1187
      }
1188
      else if (qname.equals("revision")) {
1189
        revisionString = "";
1190
      }
1191
      else if (qname.equals("documentType")) {
1192
        documentType = "";
1193
      }
1194
      else if (qname.equals("documentURL")) {
1195
        documentURL = "";
1196
      }
1197
    }
1198
  }
1199
}
(3-3/10)