source: trunk/gli/src/org/greenstone/gatherer/gems/MetadataSet.java@ 8270

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

Source files for the Greenstone Editor for Metadata Sets (GEMS). This is currently just the old MetadataEditorManager, modified to run stand-alone. It will be substantially improved by Attila Aros.

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