source: trunk/gli/src/org/greenstone/gatherer/msm/MetadataSetManager.java@ 5748

Last change on this file since 5748 was 5748, checked in by jmt12, 21 years ago

Apparently I added one space. Weird

  • Property svn:keywords set to Author Date Id Revision
File size: 54.9 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.msm;
38
39import java.io.*;
40import java.util.*;
41import javax.swing.*;
42import javax.swing.filechooser.*;
43import org.greenstone.gatherer.Configuration;
44import org.greenstone.gatherer.Dictionary;
45import org.greenstone.gatherer.Gatherer;
46import org.greenstone.gatherer.cdm.CommandTokenizer;
47import org.greenstone.gatherer.file.FileNode;
48import org.greenstone.gatherer.gui.MetaEditPrompt;
49import org.greenstone.gatherer.mem.MetadataEditorManager;
50import org.greenstone.gatherer.msm.Declarations;
51import org.greenstone.gatherer.msm.MDSFileFilter;
52import org.greenstone.gatherer.msm.MetadataSet;
53import org.greenstone.gatherer.msm.MSMAction;
54import org.greenstone.gatherer.msm.MSMEvent;
55import org.greenstone.gatherer.msm.MSMPrompt;
56import org.greenstone.gatherer.msm.MSMUtils;
57import org.greenstone.gatherer.valuetree.GValueModel;
58import org.greenstone.gatherer.valuetree.GValueNode;
59import org.greenstone.gatherer.util.MetadataXML;
60import org.greenstone.gatherer.util.Utility;
61import org.apache.xerces.parsers.*;
62import org.apache.xml.serialize.*;
63import org.w3c.dom.*;
64import org.xml.sax.*;
65
66/** This class is responsible for managing all the metadata sets in the collection and for providing methods for manipulating the aforementioned sets contents.
67 * @author John Thompson, Greenstone Digital Library, University of Waikato
68 * @version 2.3b
69 */
70public class MetadataSetManager {
71
72 /** The name of the hidden, or system, metadata set. */
73 static final public String HIDDEN = "hidden";
74
75 /** A mapping from metadata namespace to metadata set. */
76 static private Hashtable mds_hashtable = new Hashtable();
77
78 /** The class responsible for creating and maintaining all the visual components of the metadata management package. */
79 public MSMPrompt prompt = null;
80 /** The profiler is responsible for remembering what actions a user has requested when importing metadata, so as to prevent the user needlessly re-entering this information for each import. */
81 public MSMProfiler profiler = null;
82
83 /** Records all of the changes made to metadata as part of the current metadata change. Entries map from a particular FileNode to an ArrayList of the modified metadata for that node. Not to be confused with the undo managers idea of undo which records a list of all metadata changes requested. */
84 private HashMap undo_buffer = new HashMap();
85 /** The loader responsible for sequentially loading and attempting to use all registered metadata parsers to extract existing metadata from new files. */
86 private ExistingMetadataLoader loader = null;
87 /** Specialized parser for parsing GreenstoneDirectoryMetadata files, which not only caches entries, but also breaks up massive metadata.xml files into reasonable sizes. */
88 private GDMParser gdm_parser = null;
89 /** A list of classes who are interested in changes to the loaded metadata sets. */
90 private Vector listeners = null;
91
92 /** Constructor. */
93 public MetadataSetManager() {
94 this.gdm_parser = new GDMParser();
95 this.listeners = new Vector();
96 this.loader = new ExistingMetadataLoader();
97 loadProfiler();
98 this.prompt = new MSMPrompt(this);
99 }
100
101 /** Attach a piece of metadata to a record or records, ensuring the value tree is built properly, and correct messaging fired.
102 * @param records A FileNode[] of records, or directories, to add the specified metadata to.
103 * @param element The metadata element, contained within an ElementWrapper to base metadata on.
104 * @param value The value to assign to the metadata as a String.
105 */
106 public Metadata addMetadata(long id, FileNode records[], ElementWrapper element, String value_str) {
107 Metadata metadata = null;
108 if (records.length == 1) {
109 metadata = addMetadata(id, records, element, value_str, MetaEditPrompt.ACCUMULATE_ALL);
110 }
111 else {
112 metadata = addMetadata(id, records, element, value_str, MetaEditPrompt.CONFIRM);
113 }
114 return metadata;
115 }
116 public Metadata addMetadata(long id, FileNode records[], ElementWrapper element, String value_str, int action) {
117 // Retrieve the appropriate value node from the value tree for this element, creating it if necessary.
118 GValueModel model = getValueTree(element);
119 GValueNode value = null;
120 if(model != null) {
121 value = model.addValue(value_str); // Only adds if not already present, otherwise just returns existing node.
122 }
123 else {
124 value = new GValueNode(element.toString(), value_str);
125 }
126 // Create new metadata.
127 Metadata metadata = new Metadata(element, value);
128 // Reset the undo buffer.
129 undo_buffer.clear();
130 // Assign to records. Note that we must watch for responses from the user prompts, and cease loop if break signalled.
131 // Now add the metadata to each selected file node.
132 for(int i = 0; action != MetaEditPrompt.CANCEL && i < records.length; i++) {
133 action = addMetadata(id, records[i], metadata, action, (records.length > 1));
134 }
135 // If we were cancelled we should undo any changes so far
136 if(action == MetaEditPrompt.CANCEL) {
137 for(Iterator keys = undo_buffer.keySet().iterator(); keys.hasNext(); ) {
138 FileNode record = (FileNode) keys.next();
139 undoAdd(id, record);
140 }
141 }
142 return metadata;
143 }
144
145 /** Adds a metadata set listener to this set, if it isn't alreay listening.
146 * @param listener The new MSMListener.
147 */
148 public void addMSMListener(MSMListener listener) {
149 if(!listeners.contains(listener)) {
150 listeners.add(listener);
151 }
152 }
153
154 public MetadataSet addSet(String namespace, String name) {
155 MetadataSet mds = new MetadataSet(Utility.METADATA_SET_TEMPLATE);
156 mds.setAttribute("creator", "The Greenstone Librarian Interface");
157 // Calculate lastchanged to right now on this machine by this user
158 String user_name = System.getProperty("user.name");
159 String machine_name = Utility.getMachineName();
160 mds.setAttribute("lastchanged", Utility.getDateString() + " - " + user_name + " on " + machine_name);
161 // And the remaining attributes.
162 //mds.setAttribute("name", name);
163 mds.setAttribute("namespace", namespace);
164 mds_hashtable.put(namespace, mds);
165 // Add the name element.
166 mds.setName(name);
167 fireSetChanged(mds);
168 return mds;
169 }
170
171 /** Add a value tree to a given metadata element.
172 * @param element The ElementWrapper containing the element you wish to add a value tree for.
173 * @param value_tree The root Element of the value tree.
174 */
175 public void addValueTree(GValueModel model) {
176 ElementWrapper element = model.getElement();
177 String namespace = element.getNamespace();
178 MetadataSet mds = (MetadataSet) mds_hashtable.get(namespace);
179 if(mds != null) {
180 mds.addValueTree(element, model);
181 }
182 }
183 /** Destructor.
184 * @see org.greenstone.gatherer.msm.MSMProfiler
185 */
186 public void destroy() {
187 mds_hashtable.clear();
188 profiler.destroy();
189 profiler = null;
190 }
191 /** Method called to open a metadata set editing window.
192 * @return A boolean indicating if the edit was successful.
193 */
194 public boolean editMDS() {
195 MetadataEditorManager mem = new MetadataEditorManager();
196 mem.dispose();
197 mem = null;
198 return true;
199 }
200
201 /** This method is called to export a metadata set. First a prompt is displayed to gather necessary details such as which metadata set to export, where to export it to and what conditions should be applied when exporting. Once this information is gathered the static method <i>exportMDS()</i> is called with the appropriate output stream.
202 * @return A boolean which is <i>true</i> if the metadata set has been exported successfully, <i>false</i> otherwise.
203 */
204 public boolean exportMDS() {
205 ExportMDSPrompt emdsp = new ExportMDSPrompt(this, true);
206 int result = emdsp.display();
207 MetadataSet set = emdsp.getSelectedSet();
208 if (result == ExportMDSPrompt.EXPORT && set != null) {
209 File file = emdsp.getSelectedFile();
210 MetadataSet set_copy = new MetadataSet(set, emdsp.getSelectedCondition());
211 try {
212 file.getParentFile().mkdirs();
213 Utility.export(set_copy.getDocument(), file);
214 // Now for each element attempt to save its value tree.
215 NodeList elements = set_copy.getElements();
216 for(int i = elements.getLength() - 1; i >= 0; i--) {
217 ElementWrapper value_element = new ElementWrapper((Element)elements.item(i));
218 GValueModel value_tree = set_copy.getValueTree(value_element);
219 if(value_tree != null) {
220 File value_file = new File(file.getParentFile(), value_element.getName() + ".mdv");
221 ///ystem.err.println("Saving value file: " + value_file.toString());
222 Utility.export(value_tree.getDocument(), value_file);
223 }
224 }
225 return true;
226 }
227 catch (Exception error) {
228 error.printStackTrace();
229 }
230 }
231 emdsp.dispose();
232 emdsp = null;
233 return false;
234 }
235
236 /** Fire an element changed message off to all registered listeners.
237 * @param event An MSMEvent detailing the change.
238 */
239 public void fireElementChanged(MSMEvent event) {
240 // Then send it to all the listeners.
241 for(int i = 0; i < listeners.size(); i++) {
242 ((MSMListener)listeners.get(i)).elementChanged(event);
243 }
244 }
245
246 /** Fire a metadata value changed message, whose id is to be generated now, off to all registered listeners. */
247 public void fireMetadataChanged(FileNode node, Metadata old_data, Metadata new_data) {
248 fireMetadataChanged(System.currentTimeMillis(), node, old_data, new_data);
249 }
250
251 /** Fire a metadata value changed message off to all registered listeners. */
252 public void fireMetadataChanged(long id, FileNode node, Metadata old_data, Metadata new_data) {
253 if(old_data != null) {
254 old_data.getElement().dec();
255 }
256 if(new_data != null) {
257 new_data.getElement().inc();
258 }
259 ///ystem.err.println("Metadata changed: " + record + " > '" + old_data + "' -> '" + new_data + "'");
260 // Create a new MSMEvent based on the record.
261 MSMEvent event = new MSMEvent(this, id, node, old_data, new_data);
262 // Then send it to all the listeners.
263 for(int i = 0; i < listeners.size(); i++) {
264 ((MSMListener)listeners.get(i)).metadataChanged(event);
265 }
266 }
267
268 /** Fire a metadata set changed message off to all registered listeners.
269 * @param set The MetadataSet thats changed.
270 */
271 public void fireSetChanged(MetadataSet set) {
272 // Create a new MSMEvent, with a MSMAction containing only the new set.
273 MSMEvent event = new MSMEvent(this, 0L, new MSMAction(set.toString(), null, -1, null));
274 // Then send it to all the listeners.
275 for(int i = 0; i < listeners.size(); i++) {
276 ((MSMListener)listeners.get(i)).setChanged(event);
277 }
278 }
279 /** Called whenever the value tree associated with an element changes significantly.
280 * @param element The metadata element whose value tree has changed, as an ElementWrapper.
281 */
282 public void fireValueChanged(ElementWrapper element, GValueModel old_model, GValueModel new_model) {
283 // Create a new MSMEvent based on the element wrapper.
284 MSMEvent event = new MSMEvent(this, 0L, element, old_model, new_model);
285 // Then send it to all the listeners.
286 for(int i = 0; i < listeners.size(); i++) {
287 ((MSMListener)listeners.get(i)).valueChanged(event);
288 }
289 }
290 /** Builds a list of elements that have been assigned as metadata in this collection. We go through all of the elements, looking for elements whose occurances are greater than 0. A convenience call to the version with one parameter.
291 * @return A Vector of assigned elements.
292 */
293 public Vector getAssignedElements() {
294 return getAssignedElements(false);
295 }
296 /** Builds a list of elements that have been assigned as metadata in this collection. We go through all of the elements, looking for elements whose occurances are greater than 0.
297 * @param hierarchy_only <i>true</i> to only return those elements that are both assigned and have hierarchical value trees, <i>false</i> for just assignments.
298 * @return A Vector of assigned elements.
299 */
300 public Vector getAssignedElements(boolean hierarchy_only) {
301 Vector elements = new Vector();
302 for(Enumeration keys = mds_hashtable.keys(); keys.hasMoreElements(); ) {
303 MetadataSet mds = (MetadataSet)mds_hashtable.get(keys.nextElement());
304 if(!mds.getNamespace().equals(HIDDEN)) {
305 NodeList set_elements = mds.getElements();
306 for(int i = 0; i < set_elements.getLength(); i++) {
307 ElementWrapper element = new ElementWrapper((Element)set_elements.item(i));
308 elements.add(element);
309 }
310 }
311 }
312 Collections.sort(elements);
313 for(int i = elements.size(); i != 0; i--) {
314 ElementWrapper element = (ElementWrapper) elements.get(i - 1);
315 if(element.getOccurances() == 0 && element.getNamespace().length() > 0 && !element.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE)) {
316 elements.remove(element);
317 }
318 else if(hierarchy_only) {
319 GValueModel model = getValueTree(element);
320 if(!model.isHierarchy()) {
321 elements.remove(element);
322 }
323 }
324 }
325 return elements;
326 }
327 /** Used to get all the (non-hidden) elements in this manager.
328 * @return A Vector of ElementWrappers.
329 */
330 public Vector getElements() {
331 return getElements(false);
332 }
333
334 public Vector getElements(boolean all) {
335 return getElements(all, false);
336 }
337
338 /** Used to get all the elements in this manager.
339 * @param all <i>true</i> if all elements, including hidden, should be returned.
340 * @return A Vector of ElementWrappers.
341 */
342 public Vector getElements(boolean all, boolean force_extracted) {
343 Vector all_elements = new Vector();
344 for(Enumeration keys = mds_hashtable.keys(); keys.hasMoreElements(); ) {
345 MetadataSet mds = (MetadataSet)mds_hashtable.get(keys.nextElement());
346 if((!mds.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE) && !mds.getNamespace().equals(HIDDEN))
347 || (mds.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE) && (Gatherer.config.get("general.view_extracted_metadata", Configuration.COLLECTION_SPECIFIC) || force_extracted))
348 || (mds.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE) && mds.getNamespace().equals(HIDDEN) && all)) {
349 NodeList set_elements = mds.getElements();
350 ///ystem.err.println("The set " + mds + " has " + set_elements.getLength() + " elements.");
351 for(int i = 0; i < set_elements.getLength(); i++) {
352 Element raw_element = (Element)set_elements.item(i);
353 ElementWrapper element = new ElementWrapper(raw_element);
354 // For now we do not add subfield elements and their parents, just the subfields.
355 NodeList child_elements = raw_element.getElementsByTagName("Element");
356 if(child_elements.getLength() == 0) {
357 all_elements.add(element);
358 }
359 }
360 }
361 }
362 Collections.sort(all_elements);
363 return all_elements;
364 }
365 /** Returns all the elements within this set as a combobox model.
366 * @return A MetadataComboBoxModel containing all the metadata elements from all the sets, with namespacing.
367 */
368 public MetadataComboBoxModel getElementModel() {
369 return new MetadataComboBoxModel(this);
370 }
371 /** Retrieve a metadata element by its index.
372 * @param index The specified index as an int.
373 * @return An ElementWrapper containing the specied element, or <i>null</i> is no such element exists.
374 */
375 public ElementWrapper getElement(int index) {
376 Vector elements = getElements(false);
377 ElementWrapper result = null;
378 if(0 <= index && index < elements.size()) {
379 result = (ElementWrapper) elements.get(index);
380 }
381 return result;
382 }
383 /** Retrieve a metadata element by looking at the current metadata element. Note that this 'index' element may now be disconnected from the DOM model, so we have to reload the target element by the string method.
384 * @param element The possibly out-of-data MetadataElement.
385 * @return An ElementWrapper containing the specied element, or <i>null</i> is no such element exists.
386 */
387 public ElementWrapper getElement(ElementWrapper element) {
388 return getElement(element.toString());
389 }
390 /** Retrieve a metadata element by its fully qualified name.
391 * @param name The elements name as a String.
392 * @return An ElementWrapper containing the specied element, or <i>null</i> is no such element exists.
393 */
394 public ElementWrapper getElement(String name) {
395 return getElement(name, false);
396 }
397
398 public ElementWrapper getElement(String name, boolean perfect) {
399 ///ystem.err.println("Retrieve element " + name);
400 if(name == null) {
401 ///ystem.err.println("No name!");
402 return null;
403 }
404 ElementWrapper result = null;
405 MetadataSet set = null;
406 String element = null;
407 // First we seperate off what set it is in, where we have '<set><namespace_separator><element>'.
408 if(name.indexOf(MSMUtils.NS_SEP) != -1) {
409 String namespace = name.substring(0, name.indexOf(MSMUtils.NS_SEP));
410 // Retrieve the correct set if possible.
411 set = (MetadataSet)mds_hashtable.get(namespace);
412 namespace = null;
413 // Now retrieve the element name.
414 element = name.substring(name.indexOf(MSMUtils.NS_SEP) + 1);
415 }
416 // If we are looking for a perfect match, we can assume that no namespace means extracted metadata
417 else if(!perfect) {
418 // No namespace so assume that its extracted metadata.
419 set = (MetadataSet)mds_hashtable.get(Utility.EXTRACTED_METADATA_NAMESPACE);
420 element = name;
421 }
422 if(set != null) {
423 // Now we have a set we are ready to locate the requested element. Break the remaining element name down by the subfield separator, attempting to retrieve the element indicated by each step.
424 if(element.indexOf(MSMUtils.SF_SEP) != -1) {
425 StringTokenizer tokenizer = new StringTokenizer(element, MSMUtils.SF_SEP);
426 // Has to be at least two tokens
427 if(tokenizer.countTokens() >= 2) {
428 Element current_element = set.getElement(tokenizer.nextToken());
429 while(current_element != null && tokenizer.hasMoreTokens()) {
430 current_element = set.getElement(current_element, tokenizer.nextToken());
431 }
432 if(current_element != null) {
433 result = new ElementWrapper(current_element);
434 current_element = null;
435 }
436 }
437 tokenizer = null;
438 }
439 // No subfields - much easier.
440 if(result == null) {
441 ///ystem.err.print("Trying to match element " + element +"?");
442 Element temp = set.getElement(element);
443 if(temp != null) {
444 result = new ElementWrapper(temp);
445 }
446 temp = null;
447 }
448 set = null;
449 }
450 element = null;
451 return result;
452 }
453 /** Retrieve a certain named element from a certain named set.
454 * @param set The metadata set whose element you want.
455 * @param name The name of the element.
456 * @return An ElementWrapper around the requested element, or null if no such set or element.
457 */
458 public ElementWrapper getElement(String set, String name) {
459 if(mds_hashtable.containsKey(set)) {
460 MetadataSet temp = (MetadataSet)mds_hashtable.get(set);
461 return new ElementWrapper(temp.getElement(name));
462 }
463 return null;
464 }
465 /** Get all of the metadata elements as an array of nodelists.
466 * @return A NodeList[] of metadata elements.
467 */
468 public NodeList[] getNodeLists() {
469 NodeList elements[] = null;
470 int index = 0;
471 elements = new NodeList[getSets().size()]; // Remember not to count hidden metadata
472 for(Enumeration keys = mds_hashtable.keys(); keys.hasMoreElements(); ) {
473 MetadataSet mds = (MetadataSet)mds_hashtable.get(keys.nextElement());
474 if(!mds.getNamespace().equals(HIDDEN)) {
475 elements[index] = mds.getElements();
476 index++;
477 }
478 }
479 return elements;
480 }
481
482 /** Retrieve the named metadata set.
483 * @param name The sets name as a String.
484 * @return The MetadataSet as named, or null if no such set.
485 */
486 public MetadataSet getSet(String name) {
487 if(mds_hashtable.containsKey(name)) {
488 return (MetadataSet) mds_hashtable.get(name);
489 }
490 else if(name.equals(HIDDEN)) {
491 return createHidden();
492 }
493 return null;
494 }
495
496 /** Method to retrieve all of the metadata sets loaded in this collection.
497 * @return A Vector of metadata sets.
498 */
499 public Vector getSets() {
500 return getSets(true);
501 }
502
503 public Vector getSets(boolean include_greenstone) {
504 Vector result = new Vector();
505 for(Enumeration keys = mds_hashtable.keys(); keys.hasMoreElements(); ) {
506 MetadataSet set = (MetadataSet)mds_hashtable.get(keys.nextElement());
507 if(!set.getNamespace().equals(HIDDEN) && (include_greenstone || !set.getNamespace().equals(Utility.EXTRACTED_METADATA_NAMESPACE))) {
508 result.add(set);
509 }
510 }
511 return result;
512 }
513
514 /** Find the total number of elements loaded.
515 * @return The element count as an int.
516 */
517 public int getSize() {
518 int count = 0;
519 if(mds_hashtable.size() > 0) {
520 for(Enumeration keys = mds_hashtable.keys(); keys.hasMoreElements();) {
521 MetadataSet mds = (MetadataSet)mds_hashtable.get(keys.nextElement());
522 if(mds.getNamespace().equals(HIDDEN)) {
523 count = count + mds.size();
524 }
525 }
526 }
527 return count;
528 }
529 /** Get the value tree that matches the given element.
530 * @param element The ElementWrapper representing the element.
531 * @return The GValueModel representing the value tree or null.
532 */
533 public GValueModel getValueTree(ElementWrapper element) {
534 GValueModel value_tree = null;
535 if(element != null) {
536 String namespace = element.getNamespace();
537 if(namespace.length() > 0) {
538 MetadataSet mds = (MetadataSet) mds_hashtable.get(namespace);
539 if(mds != null) {
540 value_tree = mds.getValueTree(element);
541 }
542 }
543 }
544 return value_tree;
545 }
546 /** This method is called to import a metadata set. First a prompt is displayed to gather necessary details such as which metadata set to import. Once this information is gathered the method <i>importMDS(File)</i> is called with the appropriate filename.
547 * @return A boolean which is <i>true</i> if the metadata set has been imported successfully, <i>false</i> otherwise.
548 */
549 public boolean importMDS() {
550 JFileChooser chooser = new JFileChooser(new File(Utility.METADATA_DIR));
551 javax.swing.filechooser.FileFilter filter = new MDSFileFilter();
552 chooser.setFileFilter(filter);
553 int returnVal = chooser.showDialog(Gatherer.g_man, Dictionary.get("MSMPrompt.File_Import"));
554 if(returnVal == JFileChooser.APPROVE_OPTION) {
555 return importMDS(chooser.getSelectedFile(), true);
556 }
557 return false;
558 }
559
560 public boolean importMDS(File mds_file, boolean user_driven) {
561 // 1. Parse the new file.
562 MetadataSet mds_new = new MetadataSet(mds_file);
563 if(user_driven) {
564 // Display a prompt asking how much of the value structure the user wishes to import.
565 // ...but only if there are some MDV files to read values from
566 FilenameFilter mdv_filter = new MDVFilenameFilter(mds_new.getNamespace());
567 File[] mdv_files = mds_file.getParentFile().listFiles(mdv_filter);
568 if (mdv_files.length > 0) {
569 ExportMDSPrompt imdsp = new ExportMDSPrompt(this, false);
570 int result = imdsp.display();
571 if(result == ExportMDSPrompt.EXPORT) { // Here export means the user didn't cancel.
572 switch(imdsp.getSelectedCondition()) {
573 case MetadataSet.NO_VALUES:
574 mds_new = new MetadataSet(mds_new, MetadataSet.NO_VALUES);
575 break;
576 case MetadataSet.SUBJECTS_ONLY:
577 mds_new = new MetadataSet(mds_new, MetadataSet.SUBJECTS_ONLY);
578 break;
579 default: // ALL_VALUES
580 // Don't do anything.
581 }
582 }
583 else {
584 mds_new = null;
585 }
586 imdsp.dispose();
587 imdsp = null;
588 }
589 }
590 // Carry on importing the new collection
591 if(mds_new != null && mds_new.getDocument() != null) {
592 String family = mds_new.getNamespace();
593 // 2. See if we have another metadata set of the same family
594 // already. If so retrieve it and merge.
595 boolean matched = false;
596 for(Enumeration keys = mds_hashtable.keys();
597 keys.hasMoreElements();) {
598 String key = (String)keys.nextElement();
599 if(key.equals(family)) {
600 matched = true;
601 MetadataSet mds_cur = (MetadataSet)mds_hashtable.get(key);
602 ///ystem.err.println("Merging " + mds_new + " into " + mds_cur);
603 MergeTask task = new MergeTask(mds_cur, mds_new);
604 task.start();
605 }
606 }
607 if(!matched) {
608 ///ystem.err.println("Mapping " + family + " to " + mds_new);
609 mds_hashtable.put(family, mds_new);
610 }
611 fireSetChanged(mds_new);
612 return true;
613 }
614 // else we cancelled for some reason.
615 return false;
616 }
617
618 private class MergeTask
619 extends Thread {
620
621 MetadataSet mds_cur;
622 MetadataSet mds_new;
623
624 MergeTask(MetadataSet mds_cur, MetadataSet mds_new) {
625 this.mds_cur = mds_cur;
626 this.mds_new = mds_new;
627 }
628
629 public void run() {
630 mergeMDS(mds_cur, mds_new);
631 // Fire setChanged() message.
632 fireSetChanged(mds_new);
633 }
634 }
635
636
637 /** Accepts .mdv files for a certain metadata set. */
638 private class MDVFilenameFilter implements FilenameFilter
639 {
640 private String mds_namespace = null;
641
642 public MDVFilenameFilter(String mds_namespace)
643 {
644 this.mds_namespace = mds_namespace.toLowerCase();
645 }
646
647 public boolean accept(File dir, String name)
648 {
649 String copy = name.toLowerCase();
650 if (copy.startsWith(mds_namespace) && copy.endsWith(".mdv")) {
651 return true;
652 }
653
654 return false;
655 }
656 }
657
658 /** This method reloads all of the metadata sets that have been marked as included in this collection by entries in the collection configuration file.
659 */
660 public void load() {
661 File source = new File(Gatherer.c_man.getCollectionMetadata());
662 File files[] = source.listFiles();
663 for(int i = 0; files != null && i < files.length; i++) {
664 if(files[i].getName().endsWith(".mds")) {
665 importMDS(files[i], false);
666 }
667 }
668 // If no current 'hidden' metadata set exists, create one.
669 if(getSet(HIDDEN) == null) {
670 createHidden();
671 }
672 }
673 /** This method takes two metadata sets, the current one and a new one, and merges them. This merge takes place at an element level falling to lower levels as necessary (using <i>mergeMDE()</i> to merge elements and <i>mergeMDV()</i> to merge value trees.
674 * @param mds_current The currently loaded MetadataSet.
675 * @param mds_new A new MetadataSet you wish to merge in.
676 * @return A boolean with value <i>true</i> indicating if the merge was successful, otherwise <i>false</i> if errors were detected.
677 */
678 public boolean mergeMDS(MetadataSet mds_cur, MetadataSet mds_new) {
679 // For a super quick check for equivelent trees, we compare the last changed values.
680 if(mds_cur.getLastChanged().equals(mds_new.getLastChanged())) {
681 // Exactly the same. Nothing to change.
682 return true;
683 }
684 // Show initial progress prompt.
685 prompt.startMerge(mds_new.size());
686 boolean cancel = false;
687 // For each element in the new set
688 for(int i = 0; !cancel && i < mds_new.size(); i++) {
689 boolean cont = false;
690 Element mde_new = mds_new.getElement(i);
691 GValueModel mde_values = mds_new.getValueTree(new ElementWrapper(mde_new));
692 // See if the element already exists in the current set
693 Element mde_cur = mds_cur.getElement(mde_new.getAttribute("name"));
694 int option = Declarations.NO_ACTION;
695 while(!cont && !cancel) {
696 // We may be dealing with a brand new element, or possibly a
697 // renamed one.
698 if(mde_cur == null) {
699 // Provide merge, rename and skip options.
700 option = prompt.mDSPrompt(mds_cur, null, mds_new, mde_new);
701 }
702 else {
703 // If the two elements have equal structure we only have
704 // to worry about merging the values.
705 if(MSMUtils.elementsEqual(mds_cur, mde_cur, mds_new, mde_new, false)) {
706 cancel = !mergeMDV(mds_cur, mde_cur, mds_new, mde_new);
707 cont = true;
708 }
709 else {
710 // Provide add, merge and skip options.
711 option = prompt.mDSPrompt(mds_cur, mde_cur, mds_new, mde_new);
712 }
713 }
714 String reason = null;
715 switch(option) {
716 case Declarations.ADD:
717 // Only available to brand new elements, this options
718 // simply adds the element to the set.
719 reason = mds_cur.addElement(mde_new, mde_values);
720 if(reason == null) {
721 cont = true;
722 }
723 else {
724 prompt.addFailed(mde_new, reason);
725 cont = false;
726 }
727 break;
728 case Declarations.CANCEL:
729 cancel = true;
730 cont = true;
731 break;
732 case Declarations.FORCE_MERGE:
733 // If the mde_cur is null, that means the users has asked
734 // to merge but hasn't choosen any element to merge with.
735 // Make the user select an element.
736 mde_cur = prompt.selectElement(mds_cur);
737 case Declarations.MERGE:
738 // This case in turn calls the mergeMDE method to perform
739 // the actual merging of the Elements.
740 if(mde_cur != null) {
741 cancel = !mergeMDE(mds_cur, mde_cur, mds_new, mde_new);
742 }
743 cont = true;
744 break;
745 case Declarations.RENAME:
746 // This case adds the Element, but requires the user to
747 // enter a unique name.
748 String new_name = prompt.rename(mde_new);
749 if(new_name != null && new_name.length() > 0) {
750 reason = mds_cur.addElement(mde_new, new_name, mde_values);
751 if(reason == null) {
752 mde_cur = mds_cur.getElement(new_name);
753 cont = true;
754 }
755 else {
756 prompt.renameFailed(mde_new, new_name, reason);
757 cont = false;
758 }
759 }
760 else {
761 if(new_name != null) {
762 prompt.renameFailed(mde_new, new_name,
763 "MSMPrompt.Invalid_Name");
764 }
765 cont = false;
766 }
767 break;
768 case Declarations.REPLACE:
769 // Removes the existing Element then adds the new.
770
771 mds_cur.removeElement(mde_cur);
772
773 reason = mds_cur.addElement(mde_new, mde_values);
774 if(reason == null) {
775 cont = true;
776 }
777 else {
778 prompt.removeFailed(mde_cur, reason);
779 cont = false;
780 }
781 break;
782 case Declarations.SKIP:
783 // Does not change the set.
784 cont = true;
785 break;
786 }
787 // Add this action to profile for later reference.
788 if(profiler == null) {
789 ///ystem.err.println("No Profiler");
790 }
791 //profiler.addAction(mds_new.getFile().getAbsolutePath(), MSMUtils.getFullName(mde_new), option, MSMUtils.getFullName(mde_cur));
792 }
793 prompt.incrementMerge();
794 }
795 prompt.endMerge();
796 return true;
797 }
798
799 /** This method allows for two metadata elements to be merged. Essentially merging existing elements give the users such options as keeping or replacing attribute elements as they are merged.
800 * @param mde_cur The Element that already exists in the current metadata sets.
801 * @param mde_new A new Element which has the same name as the current one but different data.
802 * @return A boolean that if <i>true</i> indicats the action was completed. If <i>false</i> then an error or user action has prevented the merge.
803 * TODO Implement
804 */
805 public boolean mergeMDE(MetadataSet mds_cur, Element mde_cur, MetadataSet mds_new, Element mde_new) {
806 for(Node mdn_new = mde_new.getFirstChild(); mdn_new != null; mdn_new = mdn_new.getNextSibling()) {
807 // Only merge the nodes whose name is 'Attribute'
808 if(mdn_new.getNodeName().equals("Attribute")) {
809 Element att_new = (Element) mdn_new;
810 int action = Declarations.NO_ACTION;
811 Element replace_att = null; // replace this att with the new one
812 // Unfortunately some attributes, such as author, can have several occurances, so match each in turn.
813 Element temp[] = MSMUtils.getAttributeNodesNamed(mde_cur, att_new.getAttribute("name"));
814 if (temp==null) {
815 action = Declarations.ADD;
816 } else {
817
818 // look for an exact match
819 for(int i = 0; temp != null && i < temp.length; i++) {
820 Element att_cur = temp[i];
821 if(MSMUtils.attributesEqual(att_cur, att_new)) {
822 action = Declarations.SKIP;
823 break;
824 }
825 att_cur = null;
826 }
827 if (action == Declarations.NO_ACTION) {
828 // we didn't find a match, so we have to prompt teh user for what to do
829 Object result = prompt.mDEPrompt(mde_cur, temp, mde_new, att_new);
830 if (result instanceof Integer) {
831 action = ((Integer)result).intValue();
832 } else {
833 // we have a replace action, and the returned object is the Attribute to replace
834 action = Declarations.REPLACE;
835 replace_att = (Element)result;
836 }
837 }
838 }
839
840 // now do the required action
841 switch (action) {
842 case Declarations.REPLACE:
843 // Out with the old.
844 mde_cur.removeChild(replace_att);
845 case Declarations.ADD:
846 // Simply add the new attribute. No clash is possible as we have already tested for it.
847 MSMUtils.add(mde_cur, att_new);
848 break;
849 case Declarations.SKIP:
850 // Do nothing. Move on to next attribute.
851 break;
852 case Declarations.CANCEL:
853 return false;
854 default:
855 }
856
857 att_new = null;
858 temp = null;
859 replace_att = null;
860 } // if node nmae = attribute
861 } //for each child node
862 return mergeMDV(mds_cur, mde_cur, mds_new, mde_new);
863 }
864
865 /** Merge two metadata value trees.
866 * @param mds_cur The current MetadataSet.
867 * @param mde_cur The current Element which acts as a value tree root.
868 * @param mds_new The MetadataSet we are merging in.
869 * @param mde_new The Element which acts as a value tree that we are merging in.
870 */
871 public boolean mergeMDV(MetadataSet mds_cur, Element mde_cur,
872 MetadataSet mds_new, Element mde_new) {
873 // Remember we may be asked to merge with a current mdv of null.
874 return MSMUtils.updateValueTree(mds_cur, mde_cur, mds_new, mde_new);
875 }
876
877 public void removeElement(ElementWrapper element) {
878 // Retrieve the metadata set this element belongs to.
879 String namespace = element.getNamespace();
880 MetadataSet set = (MetadataSet) mds_hashtable.get(namespace);
881 if(set != null) {
882 // Bugger. Get the old name -before- we remove the element from the set.
883 String old_name = element.toString();
884 // Remove the element.
885 set.removeElement(element.getElement());
886 // Fire event.
887 fireElementChanged(new MSMEvent(this, null, old_name));
888 }
889 else {
890 ///ystem.err.println("no such set " + namespace);
891 }
892 // No such set. No such element.
893 }
894
895 /** Remove a piece of metadata from a record or records[] and fire all the relevant events.
896 * @param records A FileNode[] of records to be changed.
897 * @param metadata The Metadata to remove.
898 */
899 public void removeMetadata(long id, Metadata metadata, FileNode records[]) {
900 // Reset undo buffer
901 undo_buffer.clear();
902 // Simplier than the others. Simply remove the metadata.
903 int action = MetaEditPrompt.CONFIRM;
904 // Now remove metadata from the selected file nodes.
905 for(int i = 0; action != MetaEditPrompt.CANCEL && i < records.length; i++) {
906 action = removeMetadata(id, records[i], metadata, action, (records.length > 1));
907 }
908 // If we were cancelled we should undo any changes so far
909 if(action == MetaEditPrompt.CANCEL) {
910 for(Iterator keys = undo_buffer.keySet().iterator(); keys.hasNext(); ) {
911 FileNode record = (FileNode) keys.next();
912 undoRemove(id, record);
913 }
914 }
915 }
916 /** Remove the specified listener from ourselves.
917 * @param listener The MSMListener in question.
918 */
919 public void removeMSMListener(MSMListener listener) {
920 listeners.remove(listener);
921 }
922
923 public void removeSet(MetadataSet set) {
924 mds_hashtable.remove(set.getNamespace());
925 fireSetChanged(set);
926 }
927
928 /** Rename the identifier of a given element to the name given.
929 * @param element The metadata element effected, as an ElementWrapper.
930 * @param new_name The String to use as the new name.
931 */
932 public void renameElement(ElementWrapper element, String new_name) {
933 Element e = element.getElement();
934 String old_name = element.toString();
935 MSMUtils.setIdentifier(e, new_name);
936 fireElementChanged(new MSMEvent(this, element, old_name));
937 old_name = null;
938 e = null;
939 }
940 /** A method to save the state of this metadata set manager. First we ensure that the names of all included metadata sets have been added to the collection configuration file, then all of the metadata sets contained are exported with full content to the collect/<col_name>/metadata/ directory.
941 */
942 public void save() {
943 // Create the correct file to save these sets to...
944 File file = new File(Gatherer.self.getCollectionMetadata());
945 // And make back ups of all existing metadata set files.
946 File temp[] = file.listFiles();
947 for(int i = temp.length - 1; i >= 0; i--) {
948 if(temp[i].getName().endsWith(".mds") || temp[i].getName().endsWith(".mdv")) {
949 File backup = new File(temp[i].getAbsolutePath() + "~");
950 backup.deleteOnExit();
951 if(!temp[i].renameTo(backup)) {
952 Gatherer.println("Error in MetadataSetManager.save(): FileRenamedException");
953 }
954 }
955 }
956 // Now save the latest versions of the metadata sets.
957 for(Enumeration keys = mds_hashtable.keys(); keys.hasMoreElements(); ) {
958 String namespace = (String) keys.nextElement();
959 MetadataSet set = (MetadataSet) mds_hashtable.get(namespace);
960 try {
961 File mds_file = new File(file, set.getNamespace() + ".mds");
962 Utility.export(set.getDocument(), mds_file);
963 set.setFile(mds_file);
964 // Now for each element attempt to save its value tree.
965 NodeList elements = set.getElements();
966 for(int i = elements.getLength() - 1; i >= 0; i--) {
967 ElementWrapper value_element = new ElementWrapper((Element)elements.item(i));
968 if(value_element.hasValueTree()) {
969 GValueModel value_tree = set.getValueTree(value_element);
970 if(value_tree != null) {
971 File value_file = new File(file, value_element.getName() + ".mdv");
972 ///ystem.err.println("Saving value file: " + value_file.toString());
973 Utility.export(value_tree.getDocument(), value_file);
974 // If this is a hierarchy element, write hierarchy file.
975 if(value_element.getNamespace().equals(MetadataSetManager.HIDDEN) || value_tree.isHierarchy()) {
976 MetadataXML.write(value_element, value_tree, this, Gatherer.c_man.getCollectionEtc());
977 }
978 }
979 }
980 }
981 }
982 catch (Exception error) {
983 error.printStackTrace();
984 }
985 }
986 profiler.save();
987 }
988 /** Given a FileNode of the original file and the new FileNode, search for any metadata, either from a greenstone directory archive xml file, or from one of the registered 'plugin' parsers.
989 * @param source The source FileNode.
990 * @param destination The new FileNode.
991 */
992 public final boolean searchForMetadata(FileNode destination, FileNode source, boolean folder_level) {
993 return searchForMetadata(destination, source, folder_level, false);
994 }
995 public final boolean searchForMetadata(FileNode destination, FileNode source, boolean folder_level, boolean dummy_run) {
996 ///atherer.println("MetadataSetManager.searchForMetadata()");
997 return loader.searchForMetadata(destination, source, folder_level, dummy_run);
998 }
999 /** Build a vector of all the metadata sets that contain an element with the given name.
1000 * @param name The name of an element as a String.
1001 * @return A Vector of metadata sets.
1002 * @see MSMPrompt (org.greenstone.gatherer.msm.MSMPrompt#selectSet)
1003 */
1004 public Vector setsThatContain(String name) {
1005 Vector result = new Vector();
1006 for(Enumeration keys = mds_hashtable.keys(); keys.hasMoreElements(); ) {
1007 MetadataSet set = (MetadataSet) mds_hashtable.get(keys.nextElement());
1008 if(set.getElement(name) != null) {
1009 result.add(set);
1010 }
1011 }
1012 return result;
1013 }
1014
1015 public final int size() {
1016 return getSets().size();
1017 }
1018
1019 /** Update a piece of metadata connected to a record or records, ensuring the value tree is built properly, and correct messaging fired.
1020 * @param records A FileNode[] of records, or directories, to add the specified metadata to.
1021 * @param element The metadata element, contained within an ElementWrapper to base metadata on.
1022 * @param value The value to assign to the metadata as a String.
1023 * @param action The default action to take in the prompt.
1024 * @param file_level If true then the metadata can be replaced normally, if false then we should actually use an add method instead.
1025 * @return The Metadata we just assigned.
1026 */
1027 public Metadata updateMetadata(long id, Metadata old_metadata, FileNode records[], String value_str, int action, boolean file_level) {
1028 // Retrieve the new value node from the same value tree as the old metadata.
1029 ElementWrapper element = old_metadata.getElement();
1030 GValueModel model = getValueTree(element);
1031 GValueNode value = null;
1032 if(model != null) {
1033 value = model.addValue(value_str);
1034 }
1035 else {
1036 value = new GValueNode(element.toString(), value_str);
1037 }
1038 // Create new metadata.
1039 Metadata new_metadata = new Metadata(value);
1040 // Reset the undo buffer
1041 undo_buffer.clear();
1042 // And update the old with it.
1043 if(action == -1) {
1044 action = MetaEditPrompt.CONFIRM;
1045 }
1046 // And then update each selection file node.
1047 for(int i = 0; action != MetaEditPrompt.CANCEL && i < records.length; i++) {
1048 action = updateMetadata(id, records[i], old_metadata, new_metadata, action, (records.length > 1), file_level);
1049 }
1050 // If we were cancelled we should undo any changes so far
1051 if(action == MetaEditPrompt.CANCEL) {
1052 for(Iterator keys = undo_buffer.keySet().iterator(); keys.hasNext(); ) {
1053 FileNode record = (FileNode) keys.next();
1054 undoUpdate(id, record);
1055 }
1056 }
1057 // All done. Any events would have been fired within the record recursion.
1058 return new_metadata;
1059 }
1060
1061 /** Add a reference to a piece of metadata to the given FileNode. The whole method gets a wee bit messy as we have to allow for several different commands from users such as accumulate / overwrite, skip just this file or cancel the whole batch. Cancelling is especially problematic as we need to rollback any changes (within reason).
1062 * It is also worth mentioning that despite its name, no actual metadata is added directly by this method. Instead a call to fireMetadataChanged() is issued, which is in turn processed by the GDMManager (which, given this method may have been called from GDMManager as well, means the cycle is complete. Um, that doesn't mean theres an infinite loop... I hope).
1063 * @param id a long unique identifier shared by all actions caused by the same gesture.
1064 * @param record the FileNode we are adding the metadata to.
1065 * @param data the new Metadata.
1066 * @param action the default action as an int. May require user interaction.
1067 * @param fire_event <i>true</i> if this action should fire a metadata changed event, <i>false</i> if we are calling this as an affect of a previous event. (Don't want an infinitely recursive loop, do we).
1068 * @param multiple_selection <i>true</i> if more than one file or folder was selected.
1069 * @return an int specifying the current action. Thus changes in lower parts of the tree continue to effect other disjoint subtrees.
1070 */
1071 private int addMetadata(long id, FileNode record, Metadata data, int action, boolean multiple_selection) {
1072 // Super special exception for accumulate all action. We are going to add this metadata anyway, regardless of whats already there, so just add it.
1073 if(action == MetaEditPrompt.ACCUMULATE_ALL || action == MetaEditPrompt.OVERWRITE_ALL) {
1074 fireMetadataChanged(id, record, null, data);
1075 }
1076 else {
1077 // Recover the metadata from this file.
1078 ArrayList metadata = Gatherer.c_man.getCollection().gdm.getMetadata(record.getFile());
1079 // Most important test, we don't have to add the metadata if its already there!
1080 if(!metadata.contains(data)) {
1081 // Record undo information for this file node.
1082 ArrayList undo = new ArrayList();
1083 // Prepare for MEP
1084 int user_action = MetaEditPrompt.ACCUMULATE;
1085 String values = "";
1086 // See if there is any existing metadata with the same name. If so make a string from all the values (bob, jim etc).
1087 int metadata_size = metadata.size();
1088 for(int i = 0; i < metadata_size; i++) {
1089 Metadata current_data = (Metadata)metadata.get(i);
1090 if(current_data.getElement().equals(data.getElement())) {
1091 if(values.length() > 0) {
1092 values = values + ", ";
1093 }
1094 values = values + current_data.getValue();
1095 }
1096 }
1097 // If we are confirming prompt for user_action.
1098 if(values.length() > 0 && action == MetaEditPrompt.CONFIRM) {
1099 MetaEditPrompt mep = new MetaEditPrompt(MetaEditPrompt.ADD_PROMPT, multiple_selection, record.getFile(), data.getElement().toString(), values, data.getValue());
1100 user_action = mep.display();
1101 }
1102 if(user_action == MetaEditPrompt.ACCUMULATE_ALL || user_action == MetaEditPrompt.CANCEL || user_action == MetaEditPrompt.OVERWRITE_ALL) {
1103 action = user_action;
1104 }
1105 // If we are overwriting we first remove all metadata with the same element, unless the metadata is non-file level is which case we leave it, and hope the accumulate vs overwrite will be followed during the determining of metadata assigned.
1106 if(action == MetaEditPrompt.OVERWRITE_ALL || user_action == MetaEditPrompt.OVERWRITE) {
1107 for(int i = metadata_size; i != 0; i--) {
1108 Metadata old_data = (Metadata)metadata.get(i - 1);
1109 if(old_data.getElement().equals(data.getElement()) && old_data.isFileLevel()) {
1110 // We have a match. Remove this metadata.
1111 fireMetadataChanged(id, record, old_data, null);
1112 // Add it to our undo buffer.
1113 undo.add(old_data);
1114 }
1115 }
1116 }
1117 // Ensure the metadata will accumulate or overwrite as the user wishes.
1118 if(user_action == MetaEditPrompt.ACCUMULATE || user_action == MetaEditPrompt.ACCUMULATE_ALL) {
1119 data.setAccumulate(true);
1120 }
1121 else if(user_action == MetaEditPrompt.OVERWRITE || user_action == MetaEditPrompt.OVERWRITE_ALL) {
1122 data.setAccumulate(false);
1123 }
1124 // Unless cancelled, add the metadata after checking we don't already have it in the metadata (obviously not if we're overwriting but we'd better check anyway). Also if we've skipped the file we should do so, but move on to the next child...
1125 if((user_action == MetaEditPrompt.ACCUMULATE || user_action == MetaEditPrompt.ACCUMULATE_ALL || user_action == MetaEditPrompt.OVERWRITE || user_action == MetaEditPrompt.OVERWRITE_ALL) && !metadata.contains(data)) {
1126 ///ystem.err.println("Adding metadata " + data);
1127 fireMetadataChanged(id, record, null, data);
1128 // The last element in undo is the new element.
1129 undo.add(data);
1130 }
1131 // Store the undo list in our undo buffer.
1132 undo_buffer.put(record, undo);
1133 }
1134 }
1135 // If we've been cancelled, roll back the addition.
1136 if(action == MetaEditPrompt.CANCEL) {
1137 undoAdd(id, record);
1138 }
1139 return action;
1140 }
1141 /** addMetadata(long, FileNode, Metadata, int, boolean) */
1142
1143 /** Create the hidden mds, used for custom classifiers. */
1144 private MetadataSet createHidden() {
1145 MetadataSet hidden_mds = new MetadataSet(Utility.METADATA_SET_TEMPLATE);
1146 hidden_mds.setAttribute("creator","The Gatherer");
1147 hidden_mds.setAttribute("contact","gatherer@greenstone");
1148 hidden_mds.setAttribute("description","A hidden metadata set used to create custom classifiers.");
1149 hidden_mds.setAttribute("family","Gatherer Hidden Metadata");
1150 hidden_mds.setAttribute("lastchanged","");
1151 hidden_mds.setAttribute("name","Gatherer Hidden Metadata");
1152 hidden_mds.setAttribute("namespace",HIDDEN);
1153 mds_hashtable.put(HIDDEN, hidden_mds);
1154 fireSetChanged(hidden_mds);
1155 return hidden_mds;
1156 }
1157
1158 /** Creates a new profiler, which in turn will attempt to load previous profile information. */
1159 private void loadProfiler() {
1160 profiler = new MSMProfiler();
1161 addMSMListener(profiler);
1162 }
1163
1164 /** In order to remove metadata from the tree you first call this method providing it with the metadata you want removed. This will remove any occurance of said metadata from the given FileNode (using fireMetadataChanged()).
1165 * @param id a unique long identifier common to all actions caused by a single gesture.
1166 * @param record the FileNode who we are removing metadata from.
1167 * @param data the <strong>Metadata</strong> you wish removed from the tree.
1168 * @param action an <i>int</i> specifying the wanted prompting action.
1169 * @param fire_event <i>true</i> if this action should fire metadata changed events.
1170 * @param multiple_selection the number of records in the selection, as an <i>int</i>. Used to determine prompt controls.
1171 * @return an <i>int</i> specifying the current action. Thus changes in lower parts of the tree continue to effect other disjoint subtrees.
1172 */
1173 private int removeMetadata(long id, FileNode record, Metadata data, int action, boolean multiple_selection) {
1174 ArrayList metadata = Gatherer.c_man.getCollection().gdm.getMetadata(record.getFile());
1175 int user_action = MetaEditPrompt.REMOVE;
1176 // See if we even have this metadata.
1177 if(metadata.contains(data)) {
1178 ArrayList undo = new ArrayList();
1179 // We do have it. If action == CONFIRM, show user prompt.
1180 if(action == MetaEditPrompt.CONFIRM) {
1181 MetaEditPrompt mep = new MetaEditPrompt(MetaEditPrompt.REMOVE_PROMPT, multiple_selection, record.getFile(), data.getElement().toString(), data.getValue(), "");
1182 user_action = mep.display();
1183 }
1184 // Set action to match the user_action under certain circumstances.
1185 if(user_action == MetaEditPrompt.CANCEL || user_action == MetaEditPrompt.REMOVE_ALL) {
1186 action = user_action;
1187 }
1188 if(action == MetaEditPrompt.REMOVE_ALL || user_action == MetaEditPrompt.REMOVE) {
1189 fireMetadataChanged(id, record, data, null);
1190 undo.add(data);
1191 }
1192 // Store undo information
1193 undo_buffer.put(record, undo);
1194 }
1195 // If we've been cancelled higher up, undo action.
1196 if(action == MetaEditPrompt.CANCEL) {
1197 undoRemove(id, record);
1198 }
1199 return action;
1200 }
1201
1202 /** Rollback any changes made as part of a single metadata add process (only valid during the action itself, ie if a user presses cancel).
1203 * @param id the unique identify of all actions created as part of a single gesture, as a <i>long</i>.
1204 * @param record the FileNode whose metadata was changed.
1205 */
1206 private void undoAdd(long id, FileNode record) {
1207 // Retrieve the undo data from the buffer
1208 ArrayList undo = (ArrayList) undo_buffer.get(record);
1209 // If there is no undo then we can't do anything, but there should be
1210 if(undo != null && undo.size() > 0) {
1211 // The last piece of data in an add actions undo buffer is the metadata that was added
1212 Metadata data = (Metadata) undo.remove(undo.size() - 1);
1213 // Remove the data
1214 fireMetadataChanged(id, record, data, null);
1215 // If we removed other metadata when adding this metadata restore it too
1216 for(int i = 0; i < undo.size(); i++) {
1217 Metadata old_data = (Metadata) undo.get(i);
1218 fireMetadataChanged(id, record, null, old_data);
1219 }
1220 }
1221 }
1222 /** Rollback any changes made as part of a single metadata remove process (only valid during the action itself, ie if a user presses cancel).
1223 * @param id the unique identify of all actions created as part of a single gesture, as a long.
1224 * @param record the FileNode metadata was removed from.
1225 */
1226 private void undoRemove(long id, FileNode record) {
1227 // Retrieve undo information
1228 ArrayList undo = (ArrayList) undo_buffer.get(record);
1229 // Ensure that we have something to undo
1230 if(undo != null && undo.size() == 1) {
1231 // The undo buffer should contain exactly one entry, the metadata removed
1232 Metadata data = (Metadata) undo.get(0);
1233 fireMetadataChanged(id, record, null, data);
1234 }
1235 }
1236 /** Roll back any changes made as part of a single metadata update process (only valid during the action itself, ie if a user presses cancel).
1237 * @param id the unique identify of all actions created as part of a single gesture, as a long.
1238 * @param record the FileNode whose metadata was changed.
1239 */
1240 private void undoUpdate(long id, FileNode record) {
1241 // Retrieve undo information
1242 ArrayList undo = (ArrayList) undo_buffer.get(record);
1243 if(undo != null && undo.size() == 2) {
1244 Metadata old_data = (Metadata) undo.get(0);
1245 Metadata new_data = (Metadata) undo.get(1);
1246 fireMetadataChanged(id, record, new_data, null);
1247 if(old_data != new_data) { // Correct reference comparison
1248 fireMetadataChanged(id, record, null, old_data);
1249 }
1250 }
1251 }
1252
1253 /** Used to update the values of one of the metadata elements within this node. Has the same trickiness as Add but only half the number of options.
1254 * @param id the unique identify of all actions created as part of a single gesture, as a <i>long</i>.
1255 * @param record the FileNode whose metadata we are changing.
1256 * @param old_data The old existing <strong>Metadata</strong>.
1257 * @param new_data The new updated <strong>Metadata</strong>.
1258 * @param action An <i>int</i> indicating what we are going to do about it.
1259 * @param multiple_selection The number of records in the selection, as an <i>int</i>. Used to determine prompt controls.
1260 * @return An <i>int</i> specifying the current action. Thus changes in lower parts of the tree continue to effect other disjoint subtrees.
1261 */
1262 private int updateMetadata(long id, FileNode record, Metadata old_data, Metadata new_data, int action, boolean multiple_selection, boolean file_level) {
1263 ArrayList metadata;
1264 if(file_level) {
1265 metadata = Gatherer.c_man.getCollection().gdm.getMetadata(record.getFile());
1266 }
1267 else {
1268 metadata = Gatherer.c_man.getCollection().gdm.getAllMetadata(record.getFile());
1269 }
1270 int user_action = MetaEditPrompt.OVERWRITE;
1271 // Standard case of updating an existing metadata value.
1272 if(metadata.contains(old_data)) {
1273 ArrayList undo = new ArrayList();
1274 // If we are to prompt the user, do so.
1275 if(action == MetaEditPrompt.CONFIRM) {
1276 MetaEditPrompt mep = new MetaEditPrompt(MetaEditPrompt.UPDATE_PROMPT, multiple_selection, record.getFile(), old_data.getElement().toString(), old_data.getValue(), new_data.getValue());
1277 user_action = mep.display();
1278 }
1279 // Some user actions should have a continuous effect.
1280 if(user_action == MetaEditPrompt.OVERWRITE_ALL || user_action == MetaEditPrompt.CANCEL) {
1281 action = user_action;
1282 }
1283 // And if the update chose update, do so.
1284 if(action == MetaEditPrompt.OVERWRITE_ALL || user_action == MetaEditPrompt.OVERWRITE || user_action == MetaEditPrompt.UPDATE_ONCE) {
1285 ///ystem.err.println("Updating:\n"+old_data+"\nto\n"+new_data);
1286 // If this is file level then we can do a normal replace
1287 if(file_level) {
1288 fireMetadataChanged(id, record, old_data, new_data);
1289 undo.add(old_data);
1290 undo.add(new_data);
1291 }
1292 // Otherwise we are dealing with someone attempting to override inherited metadata, so we actually fire an add. To this end we add new data twice to the undo buffer, thus we can detect if this has happened.
1293 else {
1294 fireMetadataChanged(id, record, null, new_data);
1295 undo.add(new_data);
1296 undo.add(new_data);
1297 }
1298 }
1299 // Store the undo information
1300 undo_buffer.put(record, undo);
1301 }
1302 // If we've been cancelled undo.
1303 if(action == MetaEditPrompt.CANCEL) {
1304 undoUpdate(id, record);
1305 }
1306 return action;
1307 }
1308}
Note: See TracBrowser for help on using the repository browser.