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

Last change on this file since 8035 was 8035, checked in by mdewsnip, 20 years ago

Moving some code around in preparation for removing the entire msm package.

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