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

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

Removed all occurrences of classes explicitly importing other classes in the same package.

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