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

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

2030111: X11R6 crash was caused by MetadataEditorManager not collectly disposing then releasing all references to its child dialogs (which it does now). Why that should cause the JScrollPane to take offence I don't know.

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