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

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

Fixed tabbing.

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