source: trunk/gli/src/org/greenstone/gatherer/cdm/ClassifierManager.java@ 4626

Last change on this file since 4626 was 4626, checked in by kjdon, 21 years ago

indented some comments, changed it so that custom classifiers are hidden rather than destroyed after they are used, fixed a couple of other bugs

  • Property svn:keywords set to Author Date Id Revision
File size: 39.1 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * <BR><BR>
9 *
10 * Author: John Thompson, Greenstone Digital Library, University of Waikato
11 *
12 * <BR><BR>
13 *
14 * Copyright (C) 1999 New Zealand Digital Library Project
15 *
16 * <BR><BR>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * <BR><BR>
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * <BR><BR>
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software
34 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
35 *########################################################################
36 */
37package org.greenstone.gatherer.cdm;
38
39import java.awt.BorderLayout;
40import java.awt.Color;
41import java.awt.Component;
42import java.awt.Dimension;
43import java.awt.GridLayout;
44import java.awt.event.ActionEvent;
45import java.awt.event.ActionListener;
46import java.awt.event.MouseAdapter;
47import java.awt.event.MouseEvent;
48import java.io.BufferedReader;
49import java.io.File;
50import java.io.FileInputStream;
51import java.io.FileOutputStream;
52import java.io.InputStream;
53import java.io.InputStreamReader;
54import java.io.ObjectInputStream;
55import java.io.ObjectOutputStream;
56import java.io.StringReader;
57import java.lang.Exception;
58import java.util.ArrayList;
59import java.util.Collections;
60import java.util.Enumeration;
61import java.util.jar.JarFile;
62import javax.swing.BorderFactory;
63import javax.swing.DefaultListCellRenderer;
64import javax.swing.JButton;
65import javax.swing.JComboBox;
66import javax.swing.JLabel;
67import javax.swing.JList;
68import javax.swing.JOptionPane;
69import javax.swing.JPanel;
70import javax.swing.JScrollPane;
71import javax.swing.JTextArea;
72import javax.swing.event.ListSelectionEvent;
73import javax.swing.event.ListSelectionListener;
74import org.apache.xerces.parsers.DOMParser;
75import org.greenstone.gatherer.Gatherer;
76import org.greenstone.gatherer.cdm.Argument;
77import org.greenstone.gatherer.cdm.CollectionDesignManager;
78import org.greenstone.gatherer.cdm.CommandTokenizer;
79import org.greenstone.gatherer.cdm.Classifier;
80import org.greenstone.gatherer.cdm.CustomClassifier;
81import org.greenstone.gatherer.cdm.DynamicListModel;
82import org.greenstone.gatherer.file.FileNode;
83import org.greenstone.gatherer.msm.MSMEvent;
84import org.greenstone.gatherer.msm.MSMListener;
85import org.greenstone.gatherer.msm.MSMUtils;
86import org.greenstone.gatherer.util.Utility;
87import org.w3c.dom.Document;
88import org.w3c.dom.Node;
89import org.xml.sax.InputSource;
90/** This class is responsible for keeping track of all the classifiers assigned to this collection, and providing methods for adding and removing them.
91 * @author John Thompson, Greenstone Digital Library, University of Waikato
92 * @version 2.3
93 */
94// ####################################################################################
95// Optimization Saving
96// ####################################################################################
97// Vector -> ArrayList + Memory, + Processor, (pos. - Processor)
98// Unnecessary global references + Memory (5Kb+)
99// ####################################################################################
100public class ClassifierManager
101 implements MSMListener {
102 /** An interface to the Gatherer, the creator of this cdm module, for access to the Greenstone installation directory. */
103 private Gatherer gatherer = null;
104 /** A reference to the CollectionDesignManager for access to other configuration managers. */
105 private CollectionDesignManager manager = null;
106 /** The controls for editing the contents of this manager. */
107 private Control controls = null;
108 /** A list of assigned classifiers. */
109 private DynamicListModel assigned = null;
110 /** A list of known, but currently unassigned, classifiers. */
111 private DynamicListModel reserve = null;
112 /** We may have somehow recieved a classifier command that are, in fact, custom classifiers which can refer to classifiers that haven't been parsed yet, so this holds a list of failed commands which are retried after the loading is complete. */
113 private ArrayList unresolved_commands = null;
114 /** Constructor.
115 * @param gatherer A reference to the <strong>Gatherer</strong> for access to the Dictionary.
116 * @param manager A reference to the <strong>CollectionDesignManager</strong> itself.
117 * @see org.greenstone.gatherer.Gatherer
118 * @see org.greenstone.gatherer.cdm.DynamicListModel
119 * @see org.greenstone.gatherer.collection.CollectionManager
120 * @see org.greenstone.gatherer.msm.MetadataSetManager
121 * @see org.greenstone.gatherer.msm.MSMListener
122 */
123 public ClassifierManager(Gatherer gatherer, CollectionDesignManager manager) {
124 this.assigned = new DynamicListModel();
125 this.gatherer = gatherer;
126 this.manager = manager;
127 this.unresolved_commands = new ArrayList();
128 loadClassifiers();
129 saveClassifiers();
130 // Register as a MSMListener.
131 Gatherer.c_man.getCollection().msm.addMSMListener(this);
132 }
133 /** Method to add a new classifier to reserve.
134 * @param classifier The new <strong>Classifier</strong>.
135 * @see org.greenstone.gatherer.cdm.DynamicListModel
136 */
137 public void addClassifier(Classifier classifier) {
138 if(!reserve.contains(classifier)) {
139 reserve.addElement(classifier);
140 }
141 }
142 /** Method to assign a classifier.
143 * @param classifier The reserve <strong>Classifier</strong> to assign.
144 * @see org.greenstone.gatherer.cdm.DynamicListModel
145 */
146 public void assignClassifier(Classifier classifier) {
147 if(!assigned.contains(classifier)) {
148 assigned.addElement(classifier);
149 classifier.setManager(this);
150 gatherer.c_man.configurationChanged();
151 }
152 }
153 /** Method to assign a classifier.
154 * @param classifier The <strong>CustomClassifier</strong> to assign.
155 * @see org.greenstone.gatherer.cdm.DynamicListModel
156 */
157 public void assignClassifier(CustomClassifier classifier) {
158 if(!assigned.contains(classifier)) {
159 assigned.addElement(classifier);
160 classifier.setManager(this);
161 gatherer.c_man.configurationChanged();
162 }
163 }
164 /** Destructor.
165 * @see org.greenstone.gatherer.Gatherer
166 * @see org.greenstone.gatherer.cdm.CollectionDesignManager
167 * @see org.greenstone.gatherer.cdm.DynamicListModel
168 */
169 public void destroy() {
170 // Deregister as a listener
171 if(gatherer.c_man != null && gatherer.c_man.msm != null) {
172 gatherer.c_man.msm.removeMSMListener(this);
173 }
174 // Null globals
175 assigned = null;
176 controls = null;
177 gatherer = null;
178 manager = null;
179 reserve = null;
180 unresolved_commands = null;
181 }
182 /** Method to retrieve the classifier with the given index.
183 * @param index The index of the desired classifier as an <i>int</i>.
184 * @return The requested Classifier as an <strong>Object</strong> or <i>null</i> if no such classifier exists.
185 * @see org.greenstone.gatherer.cdm.DynamicListModel
186 */
187 public Object getClassifier(int index) {
188 if(0 <= index && index < assigned.size()) {
189 return assigned.get(index);
190 }
191 return null;
192 }
193 /** Method to retrieve the named classifier.
194 * @param name The name of the desired classifier as a <strong>String</strong>.
195 * @return The requested <strong>Classifier</strong> or <i>null</i> if no such classifier exists.
196 * @see org.greenstone.gatherer.cdm.DynamicListModel
197 */
198 public Classifier getClassifier(String name) {
199 for(int i = 0; i < reserve.size(); i++) {
200 Classifier classifier = (Classifier)reserve.get(i);
201 if(classifier.getName().equals(name)) {
202 return classifier;
203 }
204 }
205 // No success.
206 return null;
207 }
208 /** Method to retrieve the control for this manager.
209 * @return A <strong>JPanel</strong> containing the controls.
210 */
211 public JPanel getControls() {
212 if(controls == null) {
213 controls = new Control();
214 }
215 return controls;
216 }
217 /** Called whenever a metadata element changes significantly.
218 * @param event A <strong>MSMEvent</strong> choc' full of event informationy goodness.
219 */
220 public void elementChanged(MSMEvent event) {
221 // Don't really care, as the elements dealt with here are all live references so changes like identifier change will propagate immediately.
222 }
223 /** Method to find the index of the given classifier within the assigned classifiers.
224 * @param classifier The <strong>Classifier</strong> whose index you wish to find.
225 * @return The index of the classifier as an <i>int</i>, which has a value of -1 if the classifier was not found.
226 * @see org.greenstone.gatherer.cdm.DynamicListModel
227 */
228 public int indexOf(Classifier classifier) {
229 for(int i = 0; i < assigned.size(); i++) {
230 Object elem = assigned.get(i);
231 if (elem instanceof Classifier) {
232 Classifier sibling = (Classifier)elem;
233 if(sibling.equals(classifier)) {
234 return i;
235 }
236 }
237 }
238 return -1;
239 }
240 // these two methods assume that a custome classifier can never be the same as a classifier
241 public int indexOf(CustomClassifier classifier) {
242 for(int i = 0; i < assigned.size(); i++) {
243 Object elem = assigned.get(i);
244 if (elem instanceof CustomClassifier) {
245 CustomClassifier sibling = (CustomClassifier) assigned.get(i);
246 if(sibling.equals(classifier)) {
247 return i;
248 }
249 }
250 }
251 return -1;
252 }
253 /** Method to invalidate controls after a significant change in the system state.
254 */
255 public void invalidateControls() {
256 if(controls != null) {
257 controls.destroy();
258 }
259 controls = null;
260 }
261 /** Method to load the details of a single plug-in.
262 * @param classifier The classifier <strong>File</strong> you wish to load.
263 */
264 public void loadClassifier(File classifier) {
265 ///ystem.err.println("Attempting to parse " + classifier.toString());
266 Document document = null;
267 long start;
268 long end;
269 // Run classinfo on this classifier, and then send the results for parsing.
270 try {
271 String args[] = null;
272 if(Utility.isWindows()) {
273 args = new String[4];
274 if(gatherer.config.perl_path != null) {
275 args[0] = gatherer.config.perl_path;
276 }
277 else {
278 args[0] = "Perl.exe";
279 }
280 args[1] = gatherer.config.gsdl_path + "bin" + File.separator + "script" + File.separator + "classinfo.pl";
281 args[2] = "-xml";
282 args[3] = getClassifierName(classifier);
283 }
284 else {
285 args = new String[3];
286 args[0] = "classinfo.pl";
287 args[1] = "-xml";
288 args[2] = getClassifierName(classifier);
289 }
290
291 // Create the process.
292 Runtime runtime = Runtime.getRuntime();
293 Process process = runtime.exec(args);
294 BufferedReader error_in = new BufferedReader(new InputStreamReader(process.getErrorStream()));
295 String line = "";
296 StringBuffer xml = new StringBuffer("");
297 boolean xml_content = false;
298 while((line = error_in.readLine()) != null) {
299 if(xml_content) {
300 xml.append(line);
301 xml.append("\n");
302 }
303 else if(line.trim().startsWith("<?xml")) {
304 xml_content = true;
305 xml.append(line);
306 xml.append("\n");
307 }
308 }
309 error_in = null;
310 process = null;
311 runtime = null;
312 // If something has gone horribly wrong then xml will be empty.
313 if(xml.length() != 0) {
314 // Then read the xml from the piped input stream.
315 InputSource source = new InputSource(new StringReader(xml.toString()));
316 DOMParser parser = new DOMParser();
317 parser.parse(source);
318 document = parser.getDocument();
319 parser = null;
320 source = null;
321 }
322 else {
323 String classifier_name = getClassifierName(classifier);
324 Gatherer.println("Zero length argument xml detected for: " + classifier_name);
325 JOptionPane.showMessageDialog(Gatherer.g_man, get("Classifier_XML_Parse_Failed", classifier_name), get("General.Error"), JOptionPane.ERROR_MESSAGE);
326 classifier_name = null;
327 }
328 }
329 catch (Exception error) {
330 error.printStackTrace();
331 ///ystem.err.println("Error: Cannot parse " + getClassifierName(classifier));
332 }
333 if(document != null) {
334 parse(document.getDocumentElement());
335 }
336 }
337 /** Called whenever the metadata value associated to a certain record changes. */
338 public void metadataChanged(MSMEvent event) {
339 FileNode record = event.getRecord();
340 if(record != null) {
341 for(int i = 0; i < assigned.size(); i++) {
342 Object object = assigned.get(i);
343 if(object instanceof CustomClassifier) {
344 CustomClassifier classifier = (CustomClassifier) object;
345 if(!classifier.isProcessing()) { // are we already in the middle of processing??
346 classifier.process(record);
347 }
348 }
349 }
350 }
351 }
352 /** This method attempts to parse a classifier command from a command string taken from the collection configuration file. This process is quite complex as not only must the correct classifier be matched but also all of the parameters given must be legal. If such a command is found, the classifier is immediately assigned.
353 * @param command The command <strong>String</strong> that may include classifier information.
354 * @return <i>true</i> if a classifier command was parsed, <i>false</i> otherwise.
355 * @see org.greenstone.gatherer.cdm.Argument
356 * @see org.greenstone.gatherer.cdm.Classifier
357 * @see org.greenstone.gatherer.cdm.CommandTokenizer
358 */
359 public boolean parse(String command) {
360 String command_lc = command.toLowerCase();
361 if(command_lc.startsWith("classify")) {
362 CommandTokenizer tokenizer = new CommandTokenizer(command);
363 if(tokenizer.countTokens() >= 2) {
364 tokenizer.nextToken(); // Throw away 'classifier'
365 String name = tokenizer.nextToken();
366 // Try to locate the classifier with this name.
367 Classifier classifier = getClassifier(name);
368 // And if successful start to parse the arguments.
369 if(classifier != null) {
370 // Take a copy.
371 classifier = classifier.copy();
372 String key = null;
373 while((key = tokenizer.nextToken()) != null) {
374 // Try to retrieve a matching argument.
375 Argument argument = classifier.getArgument(key);
376 if(argument != null) {
377 // Set as assigned.
378 argument.setAssigned(true);
379 // And if the argument is of a parameter type, parse a parameter.
380 if(argument.getType() != Argument.FLAG && tokenizer.hasMoreTokens()) {
381 String value = tokenizer.nextToken();
382 // special hack for metadata
383 if (argument.getType() == Argument.METADATA) {
384 value = value.replace(':', MSMUtils.NS_SEP);
385 if (value.indexOf(MSMUtils.NS_SEP)==-1){
386 value = Utility.EXTRACTED_METADATA_NAMESPACE +MSMUtils.NS_SEP+value;
387 }
388 }
389 // shoudl probably do the same for METADATUM
390
391 argument.setValue(value);
392 }
393 }
394 // Argument cannot be matched.
395 else {
396 String cur_key = key;
397 String value = tokenizer.nextToken();
398 if(value.startsWith("-")) {
399 key = value;
400 value = null;
401 }
402 else {
403 key = null;
404 }
405 String custom = classifier.getCustom();
406 if(custom == null) {
407 if(value == null) {
408 classifier.setCustom(cur_key);
409 }
410 else {
411 classifier.setCustom(cur_key + " " + value);
412 }
413 }
414 else {
415 if(value == null) {
416 classifier.setCustom(custom + " " + cur_key);
417 }
418 else {
419 classifier.setCustom(custom + " " + cur_key + " " + value);
420 }
421 }
422 }
423 }
424 assignClassifier(classifier);
425 return true;
426 }
427 else {
428 ///ystem.err.println("Unknown classifier");
429 }
430 }
431 }
432 else if(command_lc.startsWith("customclassifier")) {
433 unresolved_commands.add(command);
434 return true;
435 }
436 return false;
437 }
438 /** This method removes an assigned classifier. I was tempted to call it unassign, but remove is more consistant. Note that there is no way to remove a classifier from the reserve.
439 * @param classifier The Classifier or CustomClassifier, as an <strong>Object</strong>, to remove.
440 * @see org.greenstone.gatherer.cdm.DynamicListModel
441 */
442 public void removeClassifier(Object classifier) {
443 assigned.removeElement(classifier);
444 gatherer.c_man.configurationChanged();
445 }
446 /** Method which attempts to reparse obvious classifier commands which previously referenced unresovable Classifiers.
447 * @see org.greenstone.gatherer.cdm.Classifier
448 * @see org.greenstone.gatherer.cdm.CommandTokenizer
449 * @see org.greenstone.gatherer.cdm.CustomClassifier
450 */
451 public void reparseUnresolved() {
452 for(int i = 0; i < unresolved_commands.size(); i++) {
453 String command = (String) unresolved_commands.get(i);
454 CommandTokenizer tokenizer = new CommandTokenizer(command);
455 if(tokenizer.countTokens() >= 6) {
456 tokenizer.nextToken();// Lose customclassifier
457 // Get class name.
458 String class_name = tokenizer.nextToken();
459 // Parse arguments.
460 String replaces = null;
461 String separations = null;
462 while(tokenizer.hasMoreTokens()) {
463 String arg_name = tokenizer.nextToken();
464 if(arg_name.equalsIgnoreCase("-replaces")) {
465 replaces = tokenizer.nextToken();
466 }
467 else if (arg_name.equalsIgnoreCase("-separations")){
468 separations = tokenizer.nextToken();
469 }
470 }
471 try {
472 replaces = replaces.substring(2);
473 int index = Integer.parseInt(replaces);
474 Classifier original = (Classifier)getClassifier(index);
475 if(original != null) {
476 Class custom_classifier_class = Class.forName("org.greenstone.gatherer.cdm.custom." + class_name);
477 CustomClassifier custom_classifier = (CustomClassifier) custom_classifier_class.newInstance();
478 custom_classifier.setGatherer(gatherer);
479 custom_classifier.recreate(original, separations);
480 assigned.add(index, custom_classifier);
481 assigned.removeElement(original);
482 }
483 else {
484 ///ystem.err.println("Missing original.");
485 }
486 }
487 catch (Exception error) {
488 error.printStackTrace();
489 }
490 }
491 }
492 // Regardless of if they work, clear the commands.
493 unresolved_commands.clear();
494 }
495 /** Method to cache the current contents of reserve (known classifiers) to file.
496 * @see org.greenstone.gatherer.util.Utility
497 */
498 public void saveClassifiers() {
499 try {
500 FileOutputStream file = new FileOutputStream(Utility.BASE_DIR + "classifiers.dat");
501 ObjectOutputStream out = new ObjectOutputStream(file);
502 out.writeObject(reserve);
503 out.close();
504 }
505 catch (Exception error) {
506 }
507 }
508 /** Called when a metadata set changed significantly.
509 * @param event A <strong>MSMEvent</strong> containing information about the set change.
510 */
511 public void setChanged(MSMEvent event) {
512 // Again, we would only worry about this if we contained 'inanimate' references to elements or something, but our references are live, and controls are rebuilt everytime a pop-up is needed.
513 }
514 /** Method used to determine the number of classifiers that have been assigned.
515 * @return An <i>int</i> which is the number of classifiers.
516 */
517 public int size() {
518 return assigned.size();
519 }
520 /** Method to print out a block of classifier commands, much like you'd find in a collection configuration file.
521 * @return A <strong>String</strong> containing a series of classifier commands separated by new lines.
522 * @see org.greenstone.gatherer.cdm.Classifier
523 * @see org.greenstone.gatherer.cdm.CustomClassifier
524 */
525 public String toString() {
526 StringBuffer text = new StringBuffer();
527 for(int i = 0; i < assigned.size(); i++) {
528 Object object = assigned.get(i);
529 if(object instanceof Classifier) {
530 Classifier classifier = (Classifier) object;
531 text.append(classifier.toStringConfig());
532 }
533 else if(object instanceof CustomClassifier) {
534 CustomClassifier classifier = (CustomClassifier) object;
535 text.append(classifier.getCommand());
536 text.append("\n");
537 text.append(classifier.getCustomCommand(i));
538 }
539 text.append("\n");
540 }
541 text.append("\n");
542 return text.toString();
543 }
544 /** Called when a significant change has occured to a value tree for a certain element, however we take no further action.
545 * @param event A <strong>MSMEvent</strong> containing information relevant to the event.
546 */
547 public void valueChanged(MSMEvent event) {
548 }
549 /** Retrieve a phrase from the dictionary based on a certain key.
550 * @param key The search <strong>String</strong>.
551 * @return The matching phrase from the Dictionary.
552 */
553 private String get(String key) {
554 return get(key, null);
555 }
556 /** Retrieve a phrase from the dictionary based on a certain key and certain arguments.
557 * @param key The search <strong>String</strong>.
558 * @param args A <strong>String[]</strong> used to complete and format the returned phrase.
559 * @return The matching phrase from the Dictionary.
560 * @see org.greenstone.gatherer.Dictionary
561 * @see org.greenstone.gatherer.Gatherer
562 */
563 private String get(String key, String arg) {
564 String[] args = null;
565 if(arg != null) {
566 args = new String[1];
567 args[0] = arg;
568 }
569 if(key.indexOf(".") == -1) {
570 key = "CDM.ClassifierManager." + key;
571 }
572 return gatherer.dictionary.get(key, args);
573 }
574 /** Method to extract just the classifiers name from a file object.
575 * @param classifier The <strong>File</strong> which references a certain classifier.
576 * @return A <strong>String</strong> containing just the classifiers name, without extension.
577 */
578 private String getClassifierName(File classifier) {
579 String name = classifier.getName();
580 if(name.indexOf(".") != -1) {
581 name = name.substring(0, name.indexOf("."));
582 }
583 return name;
584 }
585 /** Method to initially load information from the standard plug-ins within the gsdl Perl library.
586 * @see org.greenstone.gatherer.cdm.DynamicListModel
587 * @see org.greenstone.gatherer.util.Utility
588 */
589 private void loadClassifiers() {
590 // Attempt to restore the cached file.
591 try {
592 FileInputStream file = new FileInputStream(Utility.BASE_DIR + "classifiers.dat");
593 ObjectInputStream input = new ObjectInputStream(file);
594 reserve = (DynamicListModel) input.readObject();
595 }
596 catch (Exception error) {
597 }
598 if(reserve == null) {
599 reserve = new DynamicListModel();
600 // Retrieve the gsdl home directory...
601 String directory = gatherer.config.gsdl_path;
602 directory = directory + "perllib" + File.separator + "classify" + File.separator;
603 loadClassifiers(new File(directory));
604 }
605 }
606 /** Method to load plug-in information from a specified directory. Of course no plug-ins may be found at this location.
607 * @param directory A <strong>File</strong> indicating the directory to be scanned for plug-ins.
608 * @see org.greenstone.gatherer.cdm.ParsingProgress
609 */
610 private void loadClassifiers(File directory) {
611 File files[] = directory.listFiles();
612 if(files != null) {
613 // Create a progress indicator.
614 ParsingProgress progress = new ParsingProgress(get("CDM.ClassifierManager.Parsing.Title"), get("CDM.ClassifierManager.Parsing.Message"), files.length);
615 for(int i = 0; i < files.length; i++) {
616 // We only want to check Perl Modules.
617 if(files[i].getName().endsWith(".pm")) {
618 loadClassifier(files[i]);
619 }
620 progress.inc();
621 }
622 progress.dispose();
623 progress.destroy();
624 progress = null;
625 }
626 }
627 /** Parses a DOM tree model turning it into a Classifier and its associated arguments.
628 * @param root The <strong>Node</strong> at the root of the DOM model.
629 * @return A newly created <strong>Classifier</strong> based on the information parsed from the DOM model.
630 * @see org.greenstone.gatherer.cdm.Argument
631 */
632 private Classifier parse(Node root) {
633 Classifier classifier = new Classifier();
634 String node_name = null;
635 for(Node node = root.getFirstChild(); node != null;
636 node = node.getNextSibling()) {
637 node_name = node.getNodeName();
638 if(node_name.equals("Name")) {
639 String name = MSMUtils.getValue(node);
640 // We can save ourselves some processing time if a classifier with this name already exists in our manager. If so retrieve it and return it.
641 Classifier existing = getClassifier(name);
642 if(existing != null) {
643 return existing;
644 }
645 classifier.setName(name);
646 }
647 else if(node_name.equals("Desc")) {
648 classifier.setDesc(MSMUtils.getValue(node));
649 }
650 // Parse the multitude of arguments.
651 else if(node_name.equals("Arguments")) {
652 for(Node arg = node.getFirstChild(); arg != null; arg = arg.getNextSibling()) {
653 node_name = arg.getNodeName();
654 // An option.
655 if(node_name.equals("Option")) {
656 Argument argument = new Argument();
657 // If its an option we parse the multitude of details an options might have.
658 for(Node det = arg.getFirstChild(); det != null; det = det.getNextSibling()) {
659 node_name = det.getNodeName();
660 if(node_name.equals("Name")) {
661 argument.setName(MSMUtils.getValue(det));
662 }
663 else if(node_name.equals("Desc")) {
664 argument.setDesc(MSMUtils.getValue(det));
665 }
666 else if(node_name.equals("Type")) {
667 argument.setType(MSMUtils.getValue(det));
668 }
669 else if(node_name.equals("Default")) {
670 argument.setDefault(MSMUtils.getValue(det));
671 }
672 else if(node_name.equals("List")) {
673 // Two final loops are required to parse lists.
674 for(Node value = det.getFirstChild(); value != null; value = value.getNextSibling()) {
675 if(value.getNodeName().equals("Value")) {
676 String key = null;
677 String desc = "";
678 for(Node subvalue = value.getFirstChild(); subvalue != null; subvalue = subvalue.getNextSibling()) {
679 node_name = subvalue.getNodeName();
680 if(node_name.equals("Name")) {
681 key = MSMUtils.getValue(subvalue);
682 }
683 else if(node_name.equals("Desc")) {
684 desc = MSMUtils.getValue(subvalue);
685 }
686 }
687 if(key != null) {
688 argument.addOption(key, desc);
689 }
690 }
691 }
692 }
693 else if(node_name.equals("Required")) {
694 String v = MSMUtils.getValue(det);
695 ///ystem.err.println("Required = " + v);
696 if(v.equalsIgnoreCase("yes")) {
697 ///ystem.err.println("Setting required to true.");
698 argument.setRequired(true);
699 }
700 }
701 }
702 classifier.addArgument(argument);
703 }
704 // A super classifier class.
705 else if(node_name.equals("ClasInfo")) {
706 Classifier super_classifier = parse(arg);
707 classifier.setSuper(super_classifier);
708 }
709 }
710 }
711 }
712 if(classifier.getName() != null) {
713 addClassifier(classifier);
714 return classifier;
715 }
716 return null;
717 }
718 /** A class which provides controls for assigned and editing classifiers. */
719 private class Control
720 extends JPanel {
721 /** Button for adding classifiers. */
722 private JButton add = null;
723 /** Button for configuring the selected classifier. */
724 private JButton configure = null;
725 /** Button to remove the selected classifier. */
726 private JButton remove = null;
727 /** A combobox containing all of the known classifiers, including those that may have already been assigned. */
728 private JComboBox classifier = null;
729 /** A list of assigned classifiers. */
730 private JList classifier_list = null;
731 /** The text area containing instructions on the use of this control. */
732 private JTextArea instructions = null;
733 /** Constructor.
734 * @see org.greenstone.gatherer.cdm.ClassifierManager.Control.AddListener
735 * @see org.greenstone.gatherer.cdm.ClassifierManager.Control.ConfigureListener
736 * @see org.greenstone.gatherer.cdm.ClassifierManager.Control.RemoveListener
737 */
738 public Control() {
739 Object classifiers[] = reserve.toArray();
740 ArrayList classifier_model = new ArrayList();
741 for(int i = 0; i < classifiers.length; i++) {
742 classifier_model.add(((Classifier)classifiers[i]).getName());
743 }
744 // Now we add custom classifiers.
745 addCustomClassifiers(classifier_model);
746 Collections.sort(classifier_model);
747 // Create
748 add = new JButton(get("Add"));
749 JPanel button_pane = new JPanel();
750 JPanel central_pane = new JPanel();
751 configure = new JButton(get("Configure"));
752 JPanel header_pane = new JPanel();
753 instructions = new JTextArea(get("Instructions"));
754 instructions.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
755 instructions.setEditable(false);
756 instructions.setLineWrap(true);
757 instructions.setRows(5);
758 instructions.setWrapStyleWord(true);
759 classifier = new JComboBox(classifier_model.toArray());
760 classifier.setEditable(false);
761 JLabel classifier_label = new JLabel(get("Classifier"));
762 classifier_list = new JList(assigned);
763 JLabel classifier_list_label = new JLabel(get("Assigned"));
764 classifier_list_label.setHorizontalAlignment(JLabel.CENTER);
765 classifier_list_label.setOpaque(true);
766 JPanel classifier_list_pane = new JPanel();
767 JPanel classifier_pane = new JPanel();
768 remove = new JButton(get("Remove"));
769 JLabel title = new JLabel(get("Title"));
770 title.setHorizontalAlignment(JLabel.CENTER);
771 title.setOpaque(true);
772 JPanel temp = new JPanel(new BorderLayout());
773 // Listeners
774 add.addActionListener(new AddListener());
775 configure.addActionListener(new ConfigureListener());
776 remove.addActionListener(new RemoveListener());
777 classifier_list.addMouseListener(new ClickListener());
778 // Layout
779 title.setBorder(BorderFactory.createEmptyBorder(0,0,2,0));
780 instructions.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
781 header_pane.setLayout(new BorderLayout());
782 header_pane.add(title, BorderLayout.NORTH);
783 header_pane.add(new JScrollPane(instructions), BorderLayout.CENTER);
784 classifier_list_label.setBorder(BorderFactory.createEmptyBorder(0,2,0,2));
785 classifier_list_pane.setLayout(new BorderLayout());
786 classifier_list_pane.add(classifier_list_label, BorderLayout.NORTH);
787 classifier_list_pane.add(new JScrollPane(classifier_list), BorderLayout.CENTER);
788 classifier_label.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
789 classifier_pane.setBorder(BorderFactory.createEmptyBorder(5,0,5,0));
790 classifier_pane.setLayout(new GridLayout(1,2));
791 classifier_pane.add(classifier_label);
792 classifier_pane.add(classifier);
793 button_pane.setLayout(new GridLayout(3,1));
794 button_pane.add(add);
795 button_pane.add(configure);
796 button_pane.add(remove);
797 // Scope these mad bordering skillz.
798 temp.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(5,0,5,0), BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(get("Controls")), BorderFactory.createEmptyBorder(2,2,2,2))));
799 temp.add(classifier_pane, BorderLayout.NORTH);
800 temp.add(button_pane, BorderLayout.SOUTH);
801 central_pane.setLayout(new BorderLayout());
802 central_pane.add(classifier_list_pane, BorderLayout.CENTER);
803 central_pane.add(temp, BorderLayout.SOUTH);
804 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
805 setLayout(new BorderLayout());
806 add(header_pane, BorderLayout.NORTH);
807 add(central_pane, BorderLayout.CENTER);
808 }
809 /** Method which acts like a destructor, tidying up references to persistant objects.
810 */
811 public void destroy() {
812 add = null;
813 classifier = null;
814 classifier_list = null;
815 configure = null;
816 instructions = null;
817 remove = null;
818 }
819 /** This method is overridden to ensure the instructions are scrolled to top, before the super classes updateUI() is called.
820 */
821 public void updateUI() {
822 if(instructions != null) {
823 instructions.setCaretPosition(0);
824 }
825 super.updateUI();
826 }
827 /** Searches and adds a list of dynamically located CustomClassifiers. Note that the classes must be located under org.greenstone.gatherer.cdm.custom and have accompaning properties files which are used as dictionaries.
828 * @param classifier_model An <strong>ArrayList</strong> which will be used as the model for the combobox listing all known Classifiers.
829 */
830 private void addCustomClassifiers(ArrayList classifier_model) {
831 //classifier_model.add("CustomAZList");
832 // Search for classifiers under the org.greenstone.gatherer.cdm.custom directory.
833 File custom_directory = new File(Utility.BASE_DIR + "classes" + File.separator + "org" + File.separator + "greenstone" + File.separator + "gatherer" + File.separator + "cdm" + File.separator + "custom");
834 if(custom_directory.exists()) {
835 File children[] = custom_directory.listFiles();
836 for(int i = 0; i < children.length; i++) {
837 String temp = children[i].getName().toLowerCase();
838 // There are a whole bunch of conditions about what files are custom classifier main classes.
839 if(temp.endsWith(".class") && temp.indexOf("$") == -1) {
840 // Determine the name of this custom classifier.
841 String name = children[i].getName();
842 name = name.substring(0, name.indexOf("."));
843 classifier_model.add(name);
844 }
845 }
846 }
847 // Search for any other CustomClassifiers within the jar file (if present)
848 File jar_file = new File(Utility.GLI_ARCHIVE);
849 if(jar_file.exists()) {
850 try {
851 JarFile jar = new JarFile(jar_file);
852 for(Enumeration entries = jar.entries(); entries.hasMoreElements(); ) {
853 String name = entries.nextElement().toString();
854 if(name.startsWith("org/greenstone/gatherer/cdm/custom/") && name.endsWith(".class") && name.indexOf("$") == -1) {
855 name = name.substring(35, name.length() - 6);
856 if(!classifier_model.contains(name)) {
857 classifier_model.add(name);
858 }
859 }
860 name = null;
861 }
862 jar = null;
863 }
864 catch (Exception error) {
865 error.printStackTrace();
866 }
867 }
868 jar_file = null;
869 }
870 /** This class listens for actions upon the add button in the controls, and if detected calls the assignClassifier() method.
871 */
872 private class AddListener
873 implements ActionListener {
874 /** Any implementation of ActionListener must include this method so that we can be informed when an action has occured on one of our target controls, so that we can add the selected Classifier.
875 * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
876 * @see org.greenstone.gatherer.Gatherer
877 * @see org.greenstone.gatherer.cdm.ArgumentConfiguration
878 * @see org.greenstone.gatherer.cdm.Classifier
879 * @see org.greenstone.gatherer.cdm.CustomClassifier
880 */
881 public void actionPerformed(ActionEvent event) {
882 String name = (String)classifier.getSelectedItem();
883 Classifier target = getClassifier(name);
884 Classifier classifier = null;
885 CustomClassifier custom_classifier = null;
886 if(target != null) {
887 classifier = target.copy();
888 }
889 else {
890 // Try to retrieve custom classifier for name.
891 try {
892 Class custom_class = Class.forName("org.greenstone.gatherer.cdm.custom." + name);
893 custom_classifier = (CustomClassifier)custom_class.newInstance();
894 custom_classifier.setGatherer(gatherer);
895 }
896 catch (Exception error) {
897 Gatherer.println("Error in ClassifierManager.AddListener.actionPerformed(): " + error);
898 Gatherer.printStackTrace(error);
899
900 }
901 // And if all else fails create a new classifier.
902 if(classifier == null && custom_classifier == null) {
903 classifier = new Classifier(name, "", null);
904 }
905 }
906 if(classifier != null) {
907 // Automatically chain to configuration. This ensures required arguments are filled out.
908 ArgumentConfiguration ac = new ArgumentConfiguration(gatherer, manager, classifier);
909 if(ac.display()) {
910 assignClassifier(classifier);
911 }
912 ac.destroy();
913 ac = null;
914 }
915 // Custom classifier
916 else {
917 // Spawn a new thread to handle this, as custom_classifiers can be processor heavy.
918 CustomClassifierTask task = new CustomClassifierTask(custom_classifier);
919 task.start();
920 }
921 }
922 }
923 /** Listens for double clicks apon the list and react as if the configure button was pushed. */
924 private class ClickListener
925 extends MouseAdapter {
926 /** Called whenever the mouse is clicked over a registered component, we use this to chain through to the configure prompt.
927 * @param event A <strong>MouseEvent</strong> containing information about the mouse click.
928 */
929 public void mouseClicked(MouseEvent event) {
930 if(event.getClickCount() == 2 ) {
931 if(!classifier_list.isSelectionEmpty()) {
932 Object object = classifier_list.getSelectedValue();
933 if(object instanceof Classifier) {
934 ArgumentConfiguration ac = new ArgumentConfiguration(gatherer, manager, (Classifier)object);
935 if(ac.display()) {
936 assigned.refresh();
937 }
938 ac.destroy();
939 ac = null;
940 }
941 else if(object instanceof CustomClassifier) {
942 CustomClassifier cc = (CustomClassifier)object;
943 if(cc.display(true)) {
944 assigned.refresh();
945 }
946 cc.hide(); // Remove gui prompt or else.
947 cc = null;
948 }
949 }
950 }
951 }
952 }
953 /** This class listens for actions upon the configure button in the controls, and if detected creates a new ArgumentConfiguration dialog box to allow for configuration.
954 */
955 private class ConfigureListener
956 implements ActionListener {
957 /** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls.
958 * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
959 * @see org.greenstone.gatherer.cdm.ArgumentConfiguration
960 * @see org.greenstone.gatherer.cdm.Classifier
961 * @see org.greenstone.gatherer.cdm.CustomClassifier
962 */
963 public void actionPerformed(ActionEvent event) {
964 if(!classifier_list.isSelectionEmpty()) {
965 Object object = classifier_list.getSelectedValue();
966 if(object instanceof Classifier) {
967 ArgumentConfiguration ac = new ArgumentConfiguration(gatherer, manager, (Classifier)object);
968 if(ac.display()) {
969 assigned.refresh();
970 }
971 ac.destroy();
972 ac = null;
973 }
974 else if(object instanceof CustomClassifier) {
975 CustomClassifier cc = (CustomClassifier)object;
976 if(cc.display(true)) {
977 assigned.refresh();
978 }
979 cc.hide(); // Remove gui prompt or else.
980 cc = null;
981 }
982 }
983 }
984 }
985
986 private class CustomClassifierTask
987 extends Thread {
988
989 private CustomClassifier custom_classifier;
990
991 CustomClassifierTask(CustomClassifier custom_classifier) {
992 this.custom_classifier = custom_classifier;
993 }
994
995 public void run() {
996 if(custom_classifier.display(true)) {
997 assignClassifier(custom_classifier);
998 }
999 custom_classifier.hide(); // Remove gui prompt or else.
1000 custom_classifier = null;
1001 }
1002 }
1003
1004 /** This class listens for actions upon the remove button in the controls, and if detected calls the removeClassifier() method.
1005 */
1006 private class RemoveListener
1007 implements ActionListener {
1008 /** Any implementation of <i>ActionListener</i> must include this method so that we can be informed when an action has occured on one of our target controls, so we can remove the selected Classifier.
1009 * @param event An <strong>ActionEvent</strong> containing information garnered from the control action.
1010 * @see org.greenstone.gatherer.cdm.Classifier
1011 * @see org.greenstone.gatherer.cdm.CustomClassifier
1012 */
1013 public void actionPerformed(ActionEvent event) {
1014 if(!classifier_list.isSelectionEmpty()) {
1015 Object object = classifier_list.getSelectedValue();
1016 if(object instanceof Classifier || object instanceof CustomClassifier) {
1017 removeClassifier(object);
1018 }
1019 }
1020 }
1021 }
1022 }
1023}
1024
1025
1026
1027
1028
1029
Note: See TracBrowser for help on using the repository browser.