source: trunk/gli/src/org/greenstone/gatherer/msm/MetadataSet.java@ 8231

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

Replaced all "Gatherer.config" with "Configuration".

  • Property svn:keywords set to Author Date Id Revision
File size: 25.7 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.net.*;
41import java.util.*;
42import org.apache.xerces.dom.*;
43import org.greenstone.gatherer.Configuration;
44import org.greenstone.gatherer.Dictionary;
45import org.greenstone.gatherer.Gatherer;
46import org.greenstone.gatherer.valuetree.GValueModel;
47import org.greenstone.gatherer.valuetree.GValueNode;
48import org.greenstone.gatherer.util.StaticStrings;
49import org.greenstone.gatherer.util.Utility;
50import org.w3c.dom.*;
51/** An semi-data class to hold details about a loaded metadata set, it also provides some methods to manipulating the data within.
52 * @author John Thompson, Greenstone Digital Library, University of Waikato
53 * @version 2.3b
54 */
55public class MetadataSet {
56 /** The <Strong>Document</strong> of the DOM model. */
57 private Document document = null;
58 /** The document <strong>Element</strong> of the DOM model. */
59 private Element root = null;
60 /** The <strong>File</strong> this metadata set was loaded from. */
61 private File file = null;
62 /** A mapping from metadata elements to the root element of the value trees for that element. */
63 private Hashtable value_trees = null;
64 /** The list of metadata elements which are, of course, children of the root node. */
65 private NodeList elements = null;
66
67 private String current_language_code;
68 /** The description of this metadata set. Cached as it takes more computation time. */
69 private String description = null;
70 /** The name of this metadata set. Cached as it takes more computation time. */
71 private String name = null;
72 /** An element of the tree pruning filter enumeration, that indicates all nodes in the tree should be retained. */
73 static final int ALL_VALUES = 1;
74 /** An element of the tree pruning filter enumeration, that indicates only metadata Subject nodes or higher should remain after pruning. */
75 static final int SUBJECTS_ONLY = 2;
76 /** An element of the tree pruning filter enumeration, that indicates no value nodes i.e. the entire AssignedValues subtree, should remain after pruning. */
77 static final int NO_VALUES = 3;
78
79 public MetadataSet(String metadata_template) {
80
81 this.file = new File(metadata_template);
82 this.value_trees = new Hashtable();
83 this.document = Utility.parse(metadata_template, true);
84
85 init(true); // use class loader
86 }
87
88 /** Constructor.
89 * @param file The file the metadata set should be loaded from.
90 */
91 public MetadataSet(File file) {
92 this.file = file;
93 this.value_trees = new Hashtable();
94 this.document = Utility.parse(file, false);
95
96 init(false); // don't use class loader
97 }
98
99 /** Copy constructor.
100 * @param original The original metadata set to copy from.
101 */
102 public MetadataSet(MetadataSet original) {
103 this.value_trees = new Hashtable();
104 // We have to create a new document.
105 document = new DocumentImpl(original.getDocument().getDoctype());
106 root = (Element) document.importNode(original.getDocument().getDocumentElement(), true);
107 document.appendChild(root);
108 elements = root.getElementsByTagName("Element");
109 file = original.getFile();
110 // Now for each element read in its value tree if present.
111 for(int i = elements.getLength() - 1; i >= 0; i--) {
112 ElementWrapper value_element_wrapper = new ElementWrapper((Element)elements.item(i));
113 GValueModel value_tree = original.getValueTree(value_element_wrapper);
114 Document value_document = value_tree.getDocument();
115 Document value_document_copy = new DocumentImpl(value_document.getDoctype());
116 Element value_element = value_document.getDocumentElement();
117 Element value_element_copy = (Element) value_document_copy.importNode(value_element, true);
118 value_document_copy.appendChild(value_element_copy);
119 GValueModel value_tree_copy = new GValueModel(value_element_wrapper, value_document_copy);
120 value_trees.put(value_element_wrapper, value_tree_copy);
121 }
122 }
123 /** Conditional copy constructor.
124 * @param original The original metadata set to copy from.
125 * @param condition An <i>int</i> which matches one of the tree pruning filter types.
126 */
127 public MetadataSet(MetadataSet original, int condition) {
128 this(original);
129 // Now based on condition, we may have to remove some nodes from
130 // this model.
131 switch(condition) {
132 case ALL_VALUES:
133 // Do nothing.
134 break;
135 case SUBJECTS_ONLY:
136 // For each element retrieve its AssignedValues element.
137 for(Enumeration keys = value_trees.keys(); keys.hasMoreElements(); ) {
138 ElementWrapper value_element = (ElementWrapper)keys.nextElement();
139 GValueModel value_tree = (GValueModel)value_trees.get(value_element);
140 Document value_tree_document = value_tree.getDocument();
141 Element value_tree_root_element = value_tree_document.getDocumentElement();
142 // Traverse tree and remove leaf nodes.
143 MSMUtils.traverseTree(value_tree_root_element, MSMUtils.NONE, true);
144 }
145 break;
146 case NO_VALUES:
147 // Remove assigned values trees.
148 value_trees.clear();
149 break;
150 }
151 }
152
153 /** Add a mds level attribute.
154 * @param name The name of the attribute to add as a <Strong>String</strong>.
155 * @param value The value as a <strong>String</strong>.
156 */
157 public void addAttribute(String name, String value) {
158 root.setAttribute(name, value);
159 }
160
161 /** Add a new default metadata element with the given name to this metadata set.
162 * @param name The name of this element as a <strong>String</strong>.
163 * @return An <strong>ElementWrapper</strong> around the newly created element or null if the element was not created.
164 */
165 public ElementWrapper addElement(String name, String language)
166 {
167 Text text = document.createTextNode(name);
168 Element identifier = document.createElementNS("","Attribute");
169 identifier.setAttribute("name","identifier");
170 identifier.setAttribute("language", language);
171 identifier.appendChild(text);
172 Element element = document.createElementNS("","Element");
173 element.setAttribute("name",name);
174 element.appendChild(identifier);
175 root.appendChild(element);
176 return new ElementWrapper(element);
177 }
178
179 /** Method to add a new metadata element to this metadata set, if and only if the element is not already present.
180 * @param others_element An <strong>Element</strong> we wish to add to this metadata set, that currently belongs to some other set.
181 * @param model A <strong>GValueModel</strong> value tree
182 * @return <i>null</i> if the add is successful, otherwise a <strong>String</strong> containing an error message (phrase key).
183 */
184 public String addElement(Element others_element, GValueModel model) {
185 if(!containsElement(others_element.getAttribute("name"))) {
186 // First get ownership of the new element, then add it.
187 Element our_element = (Element)document.importNode(others_element, true);
188 // add the value tree
189 root.appendChild(our_element);
190 if (model != null) {
191 addValueTree(new ElementWrapper(our_element), model);
192 }
193 return null;
194 }
195 else {
196 return "MSMPrompt.Name_Exists";
197 }
198 }
199
200 /** Method to add a new metadata element with the specified new name to this metadata set, if and only if the name is not already in use.
201 * @param others_element An <strong>Element</strong> we wish to add to this metadata set, that currently belongs to some other set.
202 * @param new_name The new name to be given this element, as a <strong>String</strong>.
203 * @param model A <strong>GValueModel</strong> value tree
204 * @return <i>null</i> if the add is successful, otherwise a <strong>String</strong> containing an error message (phrase key).
205 */
206 public String addElement(Element others_element, String new_name, GValueModel model) {
207 if(!containsElement(new_name)) {
208 // First get ownership of the new element, then add it.
209 Element our_element =
210 (Element) document.importNode(others_element, true);
211 // Change name
212 our_element.setAttribute("name", new_name);
213 // we also want to change the english identifier of this element
214 MSMUtils.setIdentifier(our_element, new_name);
215 // Add it to teh set
216 root.appendChild(our_element);
217 // add the value tree
218 if (model != null) {
219 addValueTree(new ElementWrapper(our_element), model);
220 }
221 return null;
222 }
223 else {
224 return "MSMPrompt.Name_Exists";
225 }
226 }
227 /** Add a value tree to a given metadata element.
228 * @param element The <strong>ElementWrapper</strong> containing the element you wish to add a value tree for.
229 * @param model A <strong>GValueModel</strong> value tree
230 */
231 public void addValueTree(ElementWrapper element, GValueModel model) {
232 ///ystem.err.println("Adding value tree for " + element.toString());
233 value_trees.put(element, model);
234 }
235
236 public int compare(Element e1, Element e2) {
237 int result = 0;
238 // Check that they're not the same element.
239 if(e1 != e2) {
240 int index_e1 = -1;
241 int index_e2 = -1;
242 // Locate the indexes for each element.
243 for(int i = 0; i < elements.getLength(); i++) {
244 Node element = elements.item(i);
245 if(element == e1) {
246 index_e1 = i;
247 }
248 if(element == e2) {
249 index_e2 = i;
250 }
251 }
252 if(index_e1 < index_e2) {
253 result = -1;
254 }
255 else {
256 result = 1;
257 }
258 }
259 return result;
260 }
261
262 /** A method to determine if this metadata set contains an element with a certain name (case sensitive).
263 * @param name A <strong>String</strong> which is the name of the element whose presence we are checking.
264 * @return A <i>boolean</i> which is <i>true</i> if the named element exists, <i>false</i> otherwise.
265 */
266 public boolean containsElement(String name) {
267 for(int i = 0; i < elements.getLength(); i++) {
268 Element sibling = (Element) elements.item(i);
269 String sibling_name = sibling.getAttribute("name");
270 if(sibling_name.equals(name)) {
271 return true;
272 }
273 }
274 return false;
275 }
276
277 public NamedNodeMap getAttributes() {
278 return root.getAttributes();
279 }
280
281 /** Method to retrieve the contact address of the metadata set creator.
282 * @return A <strong>String</strong> containing the address.
283 */
284 /* private String getContact() {
285 return root.getAttribute("contact");
286 } */
287 /** Method to retrieve the name of the creator of this metadata set.
288 * @return A <strong>String</strong> containing the name.
289 */
290 public String getCreator() {
291 return root.getAttribute("creator");
292 }
293 /** Method to retrieve the description of this metadata set. Note that this is language specific, so we determine the desired language from the Dictionary. If no such entry exists, first try returning the english version and failing that the first description found.
294 * @return The description as a <strong>String</strong>.
295 */
296 public String getDescription() {
297 if(current_language_code != null && !Configuration.getLanguage().equals(current_language_code)) {
298 description = null;
299 }
300 if(description == null) {
301 description = getAttribute(StaticStrings.DESCRIPTION_ELEMENT, Dictionary.get("MSM.No_Description"));
302 }
303 return description;
304 }
305
306 /** Method to retrieve the <strong>Document</strong> associated with this metadata set.
307 * @return The <strong>Document</strong> representing this metadata set.
308 */
309 public Document getDocument() {
310 return document;
311 }
312 /** Method to retrieve the metadata element indicated by an index.
313 * @param index An <i>int</i> specifying the required element.
314 * @return The <strong>Element</strong> at the index.
315 */
316 public Element getElement(int index) {
317 return (Element)elements.item(index);
318 }
319 /** This method is used to acquire a reference to the element which matches the given metadata. Note that this is not the same as <i>metadata.getElement()</i> as the reference returned by it may now be obsolete.
320 * @param metadata A <strong>Metadata</strong> object representing an element and value assignment.
321 * @return A 'live' reference to an <strong>Element</strong> which is the same as that referenced by the given metadata, or <i>null</i> if there is no such element.
322 */
323 public Element getElement(Metadata metadata) {
324 return metadata.getElement().getElement();
325 }
326 /** This method is used to acquire a reference to the element which has the name specified. Note that this is not the same as <i>metadata.getElement()</i> as the reference returned by it may now be obsolete.
327 * @param name A <strong>String</strong> stating the desired objects name.
328 * @return A 'live' reference to an <strong>Element</strong> which is the same as that referenced by the given metadata, or <i>null</i> if there is no such element.
329 */
330 public Element getElement(String name) {
331 // Strip any namespace.
332 while(name.indexOf(".") != -1 && !name.equals(".")) {
333 name = name.substring(name.indexOf(".") + 1);
334 }
335 ///ystem.err.println("Get element named " + name);
336 for(int i = 0; i < elements.getLength(); i++) {
337 Element element = (Element) elements.item(i);
338 ///ystem.err.println("Compare to: " + element.getAttribute("name"));
339 if(element.getAttribute("name").equals(name)) {
340 return element;
341 }
342 }
343 return null;
344 }
345
346 public Element getElement(Element parent_element, String name) {
347 Gatherer.println("Get element named " + name + " from " + parent_element.getAttribute("name"));
348 NodeList elements = parent_element.getElementsByTagName("Element");
349 for(int i = 0; i < elements.getLength(); i++) {
350 Element element = (Element) elements.item(i);
351 if(element.getAttribute("name").equals(name) && element.getParentNode() == parent_element) {
352 elements = null;
353 return element;
354 }
355 }
356 elements = null;
357 return null;
358 }
359 /** Method to acquire a list of all the elements in this metadata set.
360 * @return A <strong>NodeList</strong> containing all of this sets elements.
361 */
362 public NodeList getElements() {
363 return elements;
364 }
365
366 /** Method to retrieve a list of all the elements in this metadata set, sorted.
367 * @return A <strong>Vector</strong> containing all of the elements of this sets, sorted.
368 */
369 public Vector getElementsSorted() {
370 Vector elements_list = new Vector();
371 for (int i = 0; i < elements.getLength(); i++) {
372 elements_list.add(new ElementWrapper((Element) elements.item(i)));
373 }
374 Collections.sort(elements_list, MSMUtils.METADATA_COMPARATOR);
375 return elements_list;
376 }
377
378 /** Method to retrieve the original file this metadata set was created from.
379 * @return A <strong>File</strong>.
380 */
381 public File getFile() {
382 return file;
383 }
384 /** Get the last changed attribute.
385 * @return Last changed as a <strong>String</strong>.
386 */
387 public String getLastChanged() {
388 return root.getAttribute("lastchanged");
389 }
390 /** Method to get this metadata sets name. Note that this is language specific, so we determine the desired language from the Dictionary. If no such entry exists, first try returning the english version and failing that the first name found.
391 * @return A <strong>String</strong> which contains its name.
392 */
393 public String getName() {
394 if(current_language_code != null && !Configuration.getLanguage().equals(current_language_code)) {
395 name = null;
396 }
397 if(name == null) {
398 name = getAttribute(StaticStrings.NAME_ELEMENT, Dictionary.get("MSM.No_Name"));
399 }
400 return name;
401 }
402 /** Method to retrieve this metadata sets namespace.
403 * @return The namespace as a <strong>String</strong>.
404 */
405 public String getNamespace() {
406 return root.getAttribute("namespace");
407 }
408 /** Method to retrieve the root element, i.e. the Document Element, of the DOM model behind this metadata set.
409 * @return An <strong>Element</strong> which is at the root of the modal.
410 */
411 public Element getRoot() {
412 return root;
413 }
414 /** Retrieve the value tree from this set that matches the given element.
415 * @param element The target <strong>ElementWrapper</strong>.
416 * @return A <strong>GValueModel</strong> value tree, or <i>null</i> if no such element or value tree.
417 */
418 public GValueModel getValueTree(ElementWrapper element) {
419 GValueModel value_tree = null;
420 // Stinking hashtable get doesn't use the overridden equals. So I'll do a loop, which should be pretty small ie O(n) for n metadata elements.
421 for(Enumeration keys = value_trees.keys(); keys.hasMoreElements(); ) {
422 ElementWrapper sibling = (ElementWrapper) keys.nextElement();
423 if(sibling.equals(element)) {
424 value_tree = (GValueModel) value_trees.get(sibling);
425 break;
426 }
427 }
428 // If we've found no value tree, create a new one.
429 if(value_tree == null) {
430 value_tree = new GValueModel(element);
431 value_trees.put(element, value_tree);
432 }
433 return value_tree;
434 }
435 /** Remove a mds level attribute.
436 * @param name The name of the attribute to remove.
437 */
438 public void removeAttribute(String name) {
439 root.removeAttribute(name);
440 }
441 /** Method to remove the given element from this metadata set.
442 * @param element The <strong>Element</strong> to be removed.
443 */
444 public void removeElement(Element element) {
445 // we need to remove the value tree too!!
446 removeValueTree(new ElementWrapper(element));
447 root.removeChild(element);
448 }
449 /** Used to remove the value tree for a specific element.
450 * @param element The <strong>ElementWrapper</strong> whose tree you wish to remove.
451 * @return The <strong>GValueModel</strong> we just removed
452 */
453 public GValueModel removeValueTree(ElementWrapper element) {
454 for(Enumeration keys = value_trees.keys(); keys.hasMoreElements(); ) {
455 ElementWrapper sibling = (ElementWrapper) keys.nextElement();
456 if(sibling.equals(element)) {
457 GValueModel value_tree = (GValueModel) value_trees.get(sibling);
458 value_trees.remove(sibling);
459 return value_tree;
460 }
461 }
462 return null;
463 }
464 /** Set one of the mds level attributes.
465 * @param name The attribute to change.
466 * @param value its new value.
467 */
468 public void setAttribute(String name, String value) {
469 root.setAttribute(name, value);
470 }
471 /** Once the metadata set has been saved to a different location, this is used to update the file parameter.
472 * @param file The new location of this metadata set <strong>File</strong>.
473 */
474 public void setFile(File file) {
475 this.file = file;
476 }
477
478 public void setName(String name) {
479 // Retrieve the name element. We look for the first english one.
480 Element name_element = null;
481 Element metadataset_element = document.getDocumentElement();
482 NodeList name_elements = metadataset_element.getElementsByTagName(Utility.NAME_ELEMENT);
483 for(int i = 0; i < name_elements.getLength(); i++) {
484 Element possible_name_element = (Element) name_elements.item(i);
485 if(possible_name_element.getAttribute(Utility.LANGUAGE_ATTRIBUTE).equals(Utility.ENGLISH_VALUE)) {
486 // Found it.
487 name_element = possible_name_element;
488 }
489 }
490 // If there is none add one. Note that we can only add english metadata sets. Although others can edit them to add further names as necessary.
491 if(name_element == null) {
492 name_element = document.createElement(Utility.NAME_ELEMENT);
493 name_element.setAttribute(Utility.LANGUAGE_ATTRIBUTE, Utility.ENGLISH_VALUE);
494 metadataset_element.insertBefore(name_element, metadataset_element.getFirstChild());
495 }
496 // Replace the text node
497 while(name_element.hasChildNodes()) {
498 name_element.removeChild(name_element.getFirstChild());
499 }
500 name_element.appendChild(document.createTextNode(name));
501 }
502
503 /** Method to determine the number of elements in this set.
504 * @return An <i>int</i> specifying the element count.
505 */
506 public int size() {
507 return elements.getLength();
508 }
509 /** Method to translate this class into a meaningful string, which in this case is the metadata sets name.
510 * @return The metadata sets name as a <strong>String</strong>.
511 */
512 public String toString() {
513 String name = getName();
514 // If there is no given name, then use the namespace as there is garaunteed to be one of them.
515 if(name == null || name.length() == 0) {
516 name = root.getAttribute("namespace");
517 }
518 // Append namespace
519 String namespace = root.getAttribute("namespace");
520 if(namespace == null || namespace.equals("")) {
521 namespace = Utility.EXTRACTED_METADATA_NAMESPACE;
522 }
523 name = name + " (" + namespace + ")";
524 return name;
525 }
526
527 /** This method retrieves the required attribute from the Metadata Set, typically it's name or it's description. Note that this method is language dependant, and moreover supports both legacy metadata sets and the new sets optimized for multiple languages.
528 * @param element_name the name of the type of element the required information is in as a String
529 * @param default_string the value to return if no such element is found also as a String
530 * @see org.greenstone.gatherer.Configuration#getLanguage()
531 * @see org.greenstone.gatherer.Gatherer#config
532 * @see org.greenstone.gatherer.msm.MSMUtils#getValue(Node)
533 * @see org.greenstone.gatherer.util.StaticStrings#CODE_ATTRIBUTE
534 * @see org.greenstone.gatherer.util.StaticStrings#SETLANGUAGE_ELEMENT
535 */
536 private String getAttribute(String element_name, String default_string) {
537 String result = null;
538
539 // Determine the language code.
540 current_language_code = Configuration.getLanguage();
541
542 ///ystem.err.println("Searching for the " + element_name + " in " + current_language_code);
543
544 // New Metadata Set Format makes use of deferred-node-expansion to save memory - rather than create nodes for a name and description in each language, nodes which have potentially huge strings, we instead create simplier SETLANGUAGE nodes, and then only expand the one in the desired language. Of course if a user happens to change to every available language slightly more memory will be used than in the old method. For instance consider the DLS with 25 languages, each with a name node of 50 bytes and an descriptions of 500. Thus old style > 13750 bytes while new style < 600.
545 NodeList set_language_elements = document.getElementsByTagName(StaticStrings.SETLANGUAGE_ELEMENT);
546 for(int b = 0; b < set_language_elements.getLength(); b++) {
547 Element set_language_element = (Element) set_language_elements.item(b);
548 String code = set_language_element.getAttribute(StaticStrings.CODE_ATTRIBUTE).toLowerCase();
549 if(code.equals(current_language_code)) {
550 NodeList specific_elements = set_language_element.getElementsByTagName(element_name);
551 if(specific_elements.getLength() > 0) {
552 Element specific_element = (Element) specific_elements.item(0);
553 result = MSMUtils.getValue(specific_element);
554 specific_element = null;
555 }
556 specific_elements = null;
557 }
558 code = null;
559 set_language_element = null;
560 }
561 set_language_elements = null;
562 // And we may be all done
563 if(result != null) {
564 return result;
565 }
566
567 // Failing that we move on to an older style search - start by recovering all Name elements
568 NodeList possible_elements = document.getElementsByTagName(element_name);
569 // Iterate through the available names looking for the appropriate one. Also make note of the first name, then overwrite it with any english one.
570 boolean found = false;
571 for(int i = 0; !found && i < possible_elements.getLength(); i++) {
572 Element possible_element = (Element) possible_elements.item(i);
573 String possible_element_code = possible_element.getAttribute("language").toLowerCase();
574 if(possible_element_code.equals(current_language_code) || name == null) {
575 result = MSMUtils.getValue(possible_element);
576 found = true;
577 }
578 possible_element_code = null;
579 possible_element = null;
580 }
581 possible_elements = null;
582 // Failing all that set an error message
583 if(result == null) {
584 result = default_string;
585 }
586 return result;
587 }
588
589 private void init(boolean use_classloader) {
590
591 if(this.document != null) {
592 this.elements = document.getElementsByTagName("Element");
593 this.root = document.getDocumentElement();
594 // Now for each element read in its value tree if present.
595 for(int i = elements.getLength() - 1; i >= 0; i--) {
596 ElementWrapper value_element = new ElementWrapper((Element)elements.item(i));
597 File value_file = new File(file.getParentFile(), value_element.getName() + ".mdv");
598 ///ystem.err.println("Searching for " + value_file.getAbsolutePath());
599 if(value_file.exists()) {
600 Document value_document;
601 if (use_classloader) {
602 value_document = Utility.parse(value_file.toString(), true);
603 }
604 else {
605 value_document = Utility.parse(value_file, false);
606 }
607 if(value_document != null) {
608 value_trees.put(value_element, new GValueModel(value_element, value_document));
609 }
610 else {
611 Gatherer.println("Error! Missing mdv file: " + value_file.getAbsolutePath());
612 }
613 }
614 }
615 }
616 else {
617 Gatherer.println("Error! Missing mds file: " + file.getAbsolutePath());
618 }
619 }
620}
Note: See TracBrowser for help on using the repository browser.