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

Last change on this file since 4629 was 4629, checked in by mdewsnip, 21 years ago

Changed fireSetChanged() to be public, so it can be called by the MEM when a new element is added to a metadata set.

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