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: 2004-08-10 15:19:50 -0700 (Tue, 10 Aug 2004) $'
8
 * '$Revision: 2238 $'
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.0";
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 = 300;
99
	JButton pasteButton;
100
	JButton pasteDefaultsButton;
101
  Properties properties;
102
  private String schemaLocation = 
103
//    "eml://ecoinformatics.org/harvestList ../../lib/harvester/harvestList.xsd";
104
    "eml://ecoinformatics.org/harvestList harvestList.xsd";
105
  int selectedRow = -1;
106
  final JTable table;
107
  TableModel tableModel;
108
  File tempFile;
109
  String title = "Harvest List Editor";
110
  
111
  // Menu items
112
  JMenuItem exitMenuItem = new JMenuItem("Exit");
113
  JMenuItem newMenuItem = new JMenuItem("New");
114
  JMenuItem openMenuItem = new JMenuItem("Open...");
115
  JMenuItem saveMenuItem = new JMenuItem("Save");
116
  JMenuItem saveAsMenuItem = new JMenuItem("Save As...");
117
  JMenuItem validateMenuItem = new JMenuItem("Validate");
118
  
119

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

    
129

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
333
		setVisible(true);
334
  }
335

    
336

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

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

    
385

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
739
    properties = new Properties();
740

    
741
    if (propertiesFile.exists()) {
742
      try {
743
        properties.load(new FileInputStream(propertiesFile));
744
        defaultHarvestList = properties.getProperty("defaultHarvestList");
745
        defaultDocumentType = properties.getProperty("defaultDocumentType");
746
        defaultDocumentURL = properties.getProperty("defaultDocumentURL");
747
        defaultIdentifier = properties.getProperty("defaultIdentifier");
748
        defaultRevision = properties.getProperty("defaultRevision");
749
        defaultScope = properties.getProperty("defaultScope");    
750
      }
751
      catch (IOException ioe) {
752
        System.out.println("Error loading properties file: " + 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)