source: trunk/gli/src/org/greenstone/gatherer/gems/MSMUtils.java@ 8809

Last change on this file since 8809 was 8809, checked in by mdewsnip, 19 years ago

Minor changes.

  • Property svn:keywords set to Author Date Id Revision
File size: 49.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 * Author: John Thompson, Greenstone Digital Library, University of Waikato
9 *
10 * Copyright (C) 1999 New Zealand Digital Library Project
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *########################################################################
26 */
27package org.greenstone.gatherer.gems;
28
29/**************************************************************************************
30 * Title: Gatherer
31 * Description: The Gatherer: a tool for gathering and enriching a digital collection.
32 * Company: The University of Waikato
33 * Written: / /01
34 * Revised: 16/08/02 Improved
35 * 06/08/03 Bug fixes
36 * @author John Thompson, Greenstone Digital Libraries
37 * @version 2.3
38 **************************************************************************************/
39import java.io.*;
40import java.util.*;
41import org.greenstone.gatherer.Configuration;
42import org.greenstone.gatherer.cdm.CommandTokenizer;
43import org.greenstone.gatherer.util.ArrayTools;
44import org.greenstone.gatherer.util.StaticStrings;
45import org.greenstone.gatherer.util.Utility;
46import org.w3c.dom.*;
47/** This class contains a plethora of methods associated with handling the content of <strong>MetadataSet</strong>s and the <strong>Element</strong>s within. For example this is where you will find methods for comparing various parts of two <strong>MetadataSet</strong>s for equality. It also has methods for extracting common and useful data from <strong>Element</strong>s such as the AssignedValue nodes.
48 * @author John Thompson, Greenstone Digital Libraries
49 * @version 2.3
50 */
51public class MSMUtils {
52 /** Used to order metadata according to set standard element order then alphabetically by value. */
53 // static private MetadataComparator METADATA_COMPARATOR = new MetadataComparator();
54 /** An element of the enumeration of type filter. */
55 static public int NONE = 0;
56 /** An element of the enumeration of type filter. */
57 static public int VALUES = 1;
58 /** An element of the enumeration of type filter. */
59 static public int ALIASES = 2;
60 /** An element of the enumeration of type filter. */
61 static public int BOTH = 3;
62 /** The character used to separate name space from metadata element. */
63 static public char NS_SEP= '.';
64 /** The character used to separate subfields from metadata element. */
65 static public String SF_SEP= "#";
66 /** Method to add one node as a child of another, after migrating into the target document.
67 * @param parent The <strong>Node</strong> we are inserting into.
68 * @param child The original <strong>Node</strong> we are inserting. Must first be cloned into the parents document.
69 */
70 static public void add(Node parent, Node child) {
71 Document document = parent.getOwnerDocument();
72 Node new_child = document.importNode(child, true);
73 parent.appendChild(new_child);
74 }
75
76 /** Method to add an attribute element to the given element. This method makes use of the language_dependant attribute of the document to not only determine if the attribute is language dependant, but also to see whether a Language element should be created if doesn't already exist.
77 * @param element_element the Element to add the attribute element to
78 * @param attribute_name_str the name of the new attribute to add as a String
79 * @param language_code_str the two letter code String of the language this attribute is to be added as
80 * @param value_str the String to be assigned as the attribute elements value
81 * @see org.greenstone.gatherer.msm.MSMUtils#isAttributeLanguageDependant
82 * @see org.greenstone.gatherer.msm.MSMUtils#setValue(Element, String)
83 * @see org.greenstone.gatherer.util.StaticStrings#ATTRIBUTE_ELEMENT
84 * @see org.greenstone.gatherer.util.StaticStrings#CODE_ATTRIBUTE
85 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ATTRIBUTE
86 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ELEMENT
87 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
88 */
89 static public void addElementAttribute(Element element_element, String attribute_name_str, String language_code_str, String value_str) {
90 Document document = element_element.getOwnerDocument();
91 // Create the basic new attribute (everything except language attribute)
92 Element attribute_element = document.createElement(StaticStrings.ATTRIBUTE_ELEMENT);
93 attribute_element.setAttribute(StaticStrings.NAME_ATTRIBUTE, attribute_name_str);
94 MSMUtils.setValue(attribute_element, value_str);
95 // Start off by determining if we have to add this node in the new multilingual optimized way
96 if(isAttributeLanguageDependant(document, attribute_name_str)) {
97 boolean found = false;
98 // Try to retrieve a language element for the given language code
99 NodeList language_elements = element_element.getElementsByTagName(StaticStrings.LANGUAGE_ELEMENT);
100 for(int i = 0; i < language_elements.getLength(); i++) {
101 Element language_element = (Element) language_elements.item(i);
102 if(language_element.getAttribute(StaticStrings.CODE_ATTRIBUTE).equals(language_code_str)) {
103 found = true;
104 // Add attribute
105 language_element.appendChild(attribute_element);
106 }
107 language_element = null;
108 }
109 language_elements = null;
110 // If it still hasn't been found, then add it
111 if(!found) {
112 Element language_element = document.createElement(StaticStrings.LANGUAGE_ELEMENT);
113 language_element.setAttribute(StaticStrings.CODE_ATTRIBUTE, language_code_str);
114 element_element.appendChild(language_element);
115 // Add attribute
116 language_element.appendChild(attribute_element);
117 language_element = null;
118 }
119 }
120 // Just add the attribute the old fashioned way
121 else {
122 attribute_element.setAttribute(StaticStrings.LANGUAGE_ATTRIBUTE, language_code_str);
123 element_element.appendChild(attribute_element);
124 }
125 // Clean up
126 attribute_element = null;
127 document = null;
128 }
129
130 /** A method for comparing two AssignedValues trees. This compares not only the Subject hierarchies but also the values themselves.
131 * @param avt A <strong>Node</strong> which is the root of an AssignedValues tree.
132 * @param bvt The <strong>Node</strong> which is the root of the tree we wish to compare it to.
133 * @return <i>true</i> if the two trees are equal, <i>false</i> otherwise.
134 */
135 static final private boolean assignedValuesEqual(Node avt, Node bvt) {
136 if(avt == null && bvt == null) {
137 return true; // Both are null so both are equal.
138 }
139 else if(avt == null || bvt == null) {
140 // One is null and the other isn't.
141 return false;
142 }
143 else {
144 Hashtable a_map = new Hashtable();
145 getValueMappings(avt, null, a_map);
146 Hashtable b_map = new Hashtable();
147 getValueMappings(bvt, null, b_map);
148 if(a_map.size() == b_map.size()) {
149 /** @TODO - Figure out what to do now. */
150 return true;
151 }
152 }
153 return false;
154 }
155 /** A method for comparing two attribute nodes of type Node not Attr as you might think. Attr objects are used to describe the attributes of tags themselves, while in a metadata set we intend attribute nodes to describe qualities of metadata elements. It's just confusing because the two systems (DOM model and Dublin Core) are quite similar.
156 * @param an A <strong>Node</strong> representing some attribute of an element.
157 * @param bn The <strong>Node</strong> we wish to compare it to.
158 * @return <i>true</i> if and only if the attributes are equal.
159 */
160 static final public boolean attributesEqual(Node an, Node bn) {
161 // Check we are comparing apples and apples...
162 if(an.getNodeName().equals("Attribute") && bn.getNodeName().equals("Attribute")) {
163 Element ae = (Element) an;
164 Element be = (Element) bn;
165 // Ensure we are comparing the same type of attribute.
166 if(ae.getAttribute("name").equals(be.getAttribute("name"))) {
167 // And finally retrieve and compare the values.
168 if(getValue(ae).equals(getValue(be))) {
169 // We have a match.
170 return true;
171 }
172 }
173 }
174 // And if anything goes wrong we can't be dealing with equal attributes.
175 return false;
176 }
177
178 /** Remove all of the child nodes from a certain node. */
179 static final public void clear(Node parent) {
180 while(parent.hasChildNodes()) {
181 parent.removeChild(parent.getFirstChild());
182 }
183 }
184
185 /** Method to compare two metadata elements (of type Element, which is bound to get more than a bit confusing) for equality. This test may only check the structural (ie pretty much unchanging) consistancy, or may include the AssignedValue tree as well (which will be different for each collection I'd imagine).
186 * @param a_set The <strong>MetadataSet</strong> a comes from.
187 * @param ae An <strong>Element</strong>.
188 * @param b_set The <strong>MetadataSet</strong> b comes from.
189 * @param be The <strong>Element</strong> to compare it to.
190 * @param values <i>true</i> if the AssignedValues tree should also be compared, <i>false</i> otherwise.
191 * @return <i>true</i> if the elements are equal, <i>false</i> otherwise.
192 */
193 static final public boolean elementsEqual(MetadataSet a_set, Element ae, MetadataSet b_set, Element be, boolean values) {
194 // Compare Element Attr(ibutes) within the DOM, not to be confused with comparing element attributes in a Dublin Core sense...
195 NamedNodeMap aas = ae.getAttributes();
196 NamedNodeMap bas = be.getAttributes();
197 // For each attribute in a...
198 for(int i = 0; i < aas.getLength(); i++) {
199 Attr aa = (Attr)aas.item(i);
200 // Try to retrieve an attribute of the same name from b.
201 Attr ba = (Attr)bas.getNamedItem(aa.getNodeName());
202 // Now if there was no such attribute, or if the values for the
203 // two attributes are different the structures different.
204 if(ba == null || (!aa.getValue().equals(ba.getValue()))) {
205 //ystem.err.println("Attributes are not equal");
206 return false;
207 }
208 }
209 // Quickest test of children is to see we have the same number in
210 // each. Remember to modify for missing AssignedValues which have
211 // nothing to do with structure.
212 int anc = getAttributeCount(ae);
213 int bnc = getAttributeCount(be);
214 if(anc != bnc) {
215 return false;
216 }
217 // Now we compare the child nodes of the two Elements taking into
218 // account three special cases...
219 // 1. We don't test the AssignedValues element here.
220 // 2. Remember OptionList node.
221 // 3. The Attributes of each metadata element.
222 // For each child node of a.
223 for(Node an = ae.getFirstChild(); an !=null; an =an.getNextSibling()) {
224 if(an.getNodeName().equals("OptionList")) {
225 //ystem.err.println("Matching OptionLists.");
226 Node bn = getNodeFromNamed(be, "OptionList");
227 if(bn == null || !optionListsEqual(an, bn)) {
228 //ystem.err.println("OptionLists are not equal");
229 return false;
230 }
231 }
232 // Matching attributes.
233 else if(an.getNodeName().equals("Attribute")) {
234 //ystem.err.println("Matching Attributes.");
235 boolean matched = false;
236 for(Node bn = be.getFirstChild(); bn != null && !matched;
237 bn = bn.getNextSibling()) {
238 if(bn.getNodeName().equals("Attribute")) {
239 matched = attributesEqual(an, bn);
240 }
241 }
242 if(!matched) {
243 //ystem.err.println("Cannot match attribute.");
244 return false;
245 }
246 }
247 }
248 // Finally, if we've been asked to compares value trees (for some unknown reason) go ahead and compare them too.
249 if(values) {
250 GValueModel avt = a_set.getValueTree(new ElementWrapper(ae));
251 GValueModel bvt = b_set.getValueTree(new ElementWrapper(be));
252 return assignedValuesEqual(avt.getDocument().getDocumentElement(), bvt.getDocument().getDocumentElement());
253 }
254 // If we've got this far the elements match!
255 return true;
256 }
257 /** This method extracts the assigned value trees details, if any, from a certain element and stores them in an array ready to be passed as arguments to the Dictionary.
258 * @param element The <strong>Element</strong> whose values we wish to view.
259 * @return A <strong>String[]</strong> containing the details of the assigned values tree.
260 * @see org.greenstone.gatherer.Dictionary
261 */
262 static final public String[] getAssignedValuesDetails(MetadataSet mds, Element element) {
263 String details[] = null;
264 //Node avt = getNodeFromNamed(element, "AssignedValues");
265 GValueModel avt = mds.getValueTree(new ElementWrapper(element));
266 if(avt != null) {
267 Hashtable mapping = new Hashtable();
268 getValueMappings(avt.getDocument().getDocumentElement(), null, mapping);
269 ArrayList values = new ArrayList(mapping.keySet());
270 Collections.sort(values);
271 details = new String[1];
272 for(int i = 0; i < values.size(); i++) {
273 if(details[0] == null) {
274 details[0] = " " + values.get(i);
275 }
276 else {
277 details[0] = details[0] + "\n " + values.get(i);
278 }
279 }
280 mapping = null;
281 values = null;
282 }
283 avt = null;
284 return details;
285 }
286
287 /** Retrieve all of the attributes for the given element as a tree set. Note that this requires significant manipulation if the source is a multilingual optimized metadata set.
288 * @param element the Element whose attributes we wish to catalog
289 * @return a TreeSet of the attributes sorted by their natural ordering
290 * @see org.greenstone.gatherer.msm.MSMUtils#getValue(Node)
291 * @see org.greenstone.gatherer.util.StaticStrings#ATTRIBUTE_ELEMENT
292 * @see org.greenstone.gatherer.util.StaticStrings#CODE_ATTRIBUTE
293 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ATTRIBUTE
294 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ELEMENT
295 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
296 */
297 static public TreeSet getAttributes(Element element) {
298 TreeSet attribute_tree = new TreeSet();
299
300
301 // GemsLanguageManager gemsLangManager = new GemsLanguageManager("/home/arosmain/gsdl/gli/classes/xml/languages.xml");
302 GEMSLanguageManager gemsLangManager = new GEMSLanguageManager(Configuration.gsdl_path + "/gli/classes/xml/languages.xml");
303
304 String[] enabled_langs = gemsLangManager.getEnabledLanguageCodesToArray();
305
306 for(Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) {
307 if(node instanceof Element) {
308 Element some_element = (Element) node;
309 String some_element_name = some_element.getNodeName();
310 if(some_element_name.equals(StaticStrings.ATTRIBUTE_ELEMENT)) {
311 attribute_tree.add(new Attribute(some_element.getAttribute(StaticStrings.NAME_ATTRIBUTE), some_element.getAttribute(StaticStrings.LANGUAGE_ATTRIBUTE), MSMUtils.getValue(some_element)));
312 // System.out.println(some_element_name + "<- some_element nodes' value");
313
314
315 }
316 else if(some_element_name.equals(StaticStrings.LANGUAGE_ELEMENT)) {
317 ///Added by Attila on Nov 05 04
318 // System.out.println(some_element_name + "<- some_element nodes' value");
319
320
321 ////
322
323 String language_code = some_element.getAttribute(StaticStrings.CODE_ATTRIBUTE);
324 NodeList attribute_elements = some_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
325
326 for(int i = 0; i < attribute_elements.getLength(); i++) {
327 Element attribute_element = (Element) attribute_elements.item(i);
328
329
330 //System.out.println("code is:" + language_code);
331
332
333 for(int k = 0; k < enabled_langs.length; k++){
334
335 if(enabled_langs[k].trim().compareTo(some_element.getAttributes().item(0).getNodeValue().toString()) == 0){
336
337
338
339 attribute_tree.add(new Attribute(attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE), language_code, MSMUtils.getValue(attribute_element)));
340 attribute_element = null;
341
342 break;
343 }
344 //else {
345 // System.out.println(enabled_langs[k].trim().toString() + some_element.getAttributes().item(0).getNodeValue().toString());
346 // }
347
348 }
349 // enabled_languages_split[k].toLowerCase().trim();
350
351
352 // if(match_exit_flag == false)
353 // break;
354 //add language specific attr's:
355 //attribute_tree.add(new Attribute(attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE), language_code, MSMUtils.getValue(attribute_element)));
356 attribute_element = null;
357 }
358 attribute_elements = null;
359 language_code = null;
360 }
361 some_element_name = null;
362 some_element = null;
363 }
364 }
365 // attribute_tree.
366 return attribute_tree;
367 }
368
369 /** Method to count the number of Attribute nodes under a certain Element. This ignores other nodes such as #text, OptionList and AssignedValues nodes.
370 * @param element The <strong>Element</strong> whose attributes you want to count.
371 * @return An <i>int</i> which is the number of attribute nodes.
372 */
373 static final private int getAttributeCount(Node element) {
374 int count = 0;
375 for(Node n = element.getFirstChild(); n != null;
376 n = n.getNextSibling()) {
377 if(n.getNodeName().equals("Attribute")) {
378 count++;
379 }
380 }
381 return count;
382 }
383
384 /** This method is a slight variation on getNodeNamed in that it is especially written to retrieve the attribute Nodes of a certain name present under the given element.
385 * @param element The target element <strong>Node</strong>.
386 * @param name The name of the attribute you wish to return.
387 * @return An <strong>Element[]</strong> containing the attributes you requested, or <i>null</i> if no such attributes exists.
388 */
389 static final public Element[] getAttributeNodesNamed(Node element, String name) {
390 Element attributes[] = null;
391 for(Node n = element.getFirstChild(); n != null; n = n.getNextSibling()) {
392 if(n.getNodeName().equals("Attribute")) {
393 Element e = (Element)n;
394 if(e.getAttribute("name").equals(name)) {
395 if(attributes == null) {
396 attributes = new Element[1];
397 attributes[0] = e;
398 }
399 else {
400 Element temp[] = attributes;
401 attributes = new Element[temp.length + 1];
402 System.arraycopy(temp, 0, attributes, 0, temp.length);
403 attributes[temp.length] = e;
404 temp = null;
405 }
406 }
407 e = null;
408 }
409 }
410 return attributes;
411 }
412
413 /** Method to construct an elements description by retrieving the correct attribute.
414 * @param element the Element whose name we wish to retrieve
415 * @return a String which is the elements description, or an empty string if no description exists
416 * @see org.greenstone.gatherer.msm.MSMUtils#getElementAttribute
417 * @see org.greenstone.gatherer.util.StaticStrings#COMMENT_VALUE
418 * @see org.greenstone.gatherer.util.StaticStrings#DEFINITION_VALUE
419 * @see org.greenstone.gatherer.util.StaticStrings#EMPTY_STR
420 * @see org.greenstone.gatherer.util.StaticStrings#SPACE_CHARACTER
421 */
422 static public String getDescription(Element element) {
423 String language_code_str = Configuration.getLanguage();
424 StringBuffer description = new StringBuffer(StaticStrings.EMPTY_STR);
425 description.append(getElementAttribute(element, StaticStrings.DEFINITION_VALUE, language_code_str));
426 if(description.length() > 0) {
427 description.append(StaticStrings.SPACE_CHARACTER);
428 }
429 description.append(getElementAttribute(element, StaticStrings.COMMENT_VALUE, language_code_str));
430 language_code_str = null;
431 return description.toString();
432 }
433
434 /** Retrieve the value for the requested attribute in the required language. Once again this method must be aware of the differences between the old metadata sets and the new multilingual optimized ones.
435 * @param element_element the Element whose attributes we are searching through
436 * @param attribute_name_str the name of the desired attribute as a String
437 * @param language_code_str the two letter code String indicating the desired language
438 * @see org.greenstone.gatherer.msm.MSMUtils#getValue
439 * @see org.greenstone.gatherer.msm.MSMUtils#isAttributeLanguageDependant
440 * @see org.greenstone.gatherer.util.StaticStrings#ATTRIBUTE_ELEMENT
441 * @see org.greenstone.gatherer.util.StaticStrings#CODE_ATTRIBUTE
442 * @see org.greenstone.gatherer.util.StaticStrings#EMPTY_STR
443 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ATTRIBUTE
444 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ELEMENT
445 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
446 */
447 static private String getElementAttribute(Element element_element, String attribute_name_str, String language_code_str) {
448 boolean found = false;
449 String result = StaticStrings.EMPTY_STR;
450 Document document = element_element.getOwnerDocument();
451 // Determine if the attribute is language specific
452 if(isAttributeLanguageDependant(document, attribute_name_str)) {
453 NodeList language_elements = element_element.getElementsByTagName(StaticStrings.LANGUAGE_ELEMENT);
454 for(int i = 0; !found && i < language_elements.getLength(); i++) {
455 Element language_element = (Element) language_elements.item(i);
456 if(language_element.getAttribute(StaticStrings.CODE_ATTRIBUTE).equals(language_code_str)) {
457 NodeList attribute_elements = language_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
458 for(int j = 0; !found && j < attribute_elements.getLength(); j++) {
459 Element attribute_element = (Element) attribute_elements.item(j);
460 if(attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equals(attribute_name_str)) {
461 found = true;
462 result = MSMUtils.getValue(attribute_element);
463 }
464 attribute_element = null;
465 }
466 attribute_elements = null;
467 }
468 language_element = null;
469 }
470 language_elements = null;
471 }
472 else {
473 boolean first_match = false;
474 NodeList attribute_elements = element_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
475 for(int k = 0; !found && k < attribute_elements.getLength(); k++) {
476 Element attribute_element = (Element) attribute_elements.item(k);
477 // We don't want to consider those attributes found inside language elements
478 if(attribute_element.getParentNode() == element_element) {
479 ///ystem.err.println("First level");
480 String target_name_str = attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
481 String target_language_str = attribute_element.getAttribute(StaticStrings.LANGUAGE_ATTRIBUTE);
482 ///ystem.err.println("Does " + target_name_str + " equal " + attribute_name_str + "?");
483 if(attribute_name_str.equals(target_name_str)) {
484 ///ystem.err.println("Does " + target_language_str + " equal " + language_code_str + "?");
485 if(language_code_str.equals(target_language_str)) {
486 ///ystem.err.println("Perfect match!");
487 found = true;
488 result = MSMUtils.getValue(attribute_element);
489 }
490 else if((result == StaticStrings.EMPTY_STR || first_match) && isLegacyMDS(document)) {
491 ///ystem.err.println("Legacy MDS");
492 // Special case for old style documents, where the english match is good enough
493 if(target_language_str.equals(StaticStrings.ENGLISH_LANGUAGE_STR)) {
494 ///ystem.err.println("English plate.");
495 result = MSMUtils.getValue(attribute_element);
496 }
497 // Super special case where the first match is better than nothing
498 else if(result == StaticStrings.EMPTY_STR && !first_match) {
499 ///ystem.err.println("First match.");
500 first_match = true;
501 result = MSMUtils.getValue(attribute_element);
502 }
503 }
504 }
505 target_language_str = null;
506 target_name_str = null;
507 }
508 //else {
509 ///ystem.err.println("Second level");
510 //}
511 attribute_element = null;
512 }
513 attribute_elements = null;
514
515 }
516 document = null;
517 return result;
518 }
519
520 /*************************************************************************/
521 /** Method to construct an elements fully qualified name. Note that this is different from a nodes identifier. Think of name as a short, unique reference to a metadata element, whereas identifier can be much longer, language specific and non-unique.
522 * @param element An <strong>Element</strong> whose name we are interested in.
523 * @return A <strong>String</strong> representing this given elements fully namespace qualified name.
524 */
525 static final public String getFullName(Element element) {
526 return getFullName(element, "");
527
528 }
529 /*************************************************************************/
530 /** Method to construct an elements fully qualified name. Note that this is different from a nodes identifier. Think of name as a short, unique reference to a metadata element, whereas identifier can be much longer, language specific and non-unique.
531 * @param element An <strong>Element</strong> whose name we are interested in.
532 * @return A <strong>String</strong> representing this given elements fully namespace qualified name.
533 */
534 static final public String getFullName(Element element, String namespace) {
535 StringBuffer name_buffer = new StringBuffer();
536 if(element == null) {
537 return "Error";
538 }
539 // First get the root node.
540 Document document = element.getOwnerDocument();
541 Element root = document.getDocumentElement();
542 document = null;
543 // Retrieve this elements name
544 name_buffer.append(element.getAttribute("name"));
545 // Now we check if element has a parent node, other than root. If so we begin building up the full name
546 Element parent_element = (Element) element.getParentNode();
547 while(parent_element != null && parent_element != root) {
548 name_buffer.insert(0, SF_SEP);
549 name_buffer.insert(0, parent_element.getAttribute("name"));
550 parent_element = (Element)parent_element.getParentNode();
551 }
552 parent_element = null;
553 // Finally insert the namespace and we are all done.
554 if(root != null) {
555 namespace = root.getAttribute("namespace");
556 }
557 root = null;
558 // If no root, or no namespace found, assume its extracted (at least then they can't edit it)
559 if(namespace == null || namespace.equals("")) {
560 namespace = Utility.EXTRACTED_METADATA_NAMESPACE;
561 }
562 name_buffer.insert(0, NS_SEP);
563 name_buffer.insert(0, namespace);
564 namespace = null;
565 return name_buffer.toString();
566 } // static public String getFullName(Element element)
567
568 /** Method to construct an elements name (sic identifier) by retrieving the correct attribute, language specific.
569 * @param element the Element whose name we wish to retrieve
570 * @return a String which is the elements identifier, or an empty string if no identifier exists
571 * @see org.greenstone.gatherer.msm.MSMUtils#getElementAttribute
572 * @see org.greenstone.gatherer.util.StaticStrings#IDENTIFIER_VALUE
573 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
574 */
575 static final public String getIdentifier(Element element) {
576 String identifier = getElementAttribute(element, StaticStrings.IDENTIFIER_VALUE, Configuration.getLanguage());
577 // Failing the above we return the nodes name instead.
578 if(identifier == null || identifier.length() == 0) {
579 identifier = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
580 }
581 return identifier;
582 }
583
584 /** Method to retrieve from the node given, a certain child node with the specified name.
585 * @param parent The <strong>Node</strong> whose children should be searched.
586 * @param name The required nodes name as a <strong>String</strong>.
587 * @return The requested <strong>Node</strong> if it is found, <i>null</i> otherwise.
588 */
589 static final public Node getNodeFromNamed(Node parent, String name) {
590 Node child = null;
591 for(Node i = parent.getFirstChild(); i != null && child == null;
592 i = i.getNextSibling()) {
593 if(i.getNodeName().equals(name)) {
594 child = i;
595 }
596 }
597 return child;
598 }
599 /** Look for the occurances 'field' of the element and return it if found.
600 * @return An <i>int</i> which matches the number in the occurances attribute of the element, or 0 if no such attribute.
601 */
602 static final public int getOccurances(Element element) {
603 int count = 0;
604 String number = null;
605 if((number = element.getAttribute("occurances")) != null) {
606 try {
607 count = Integer.parseInt(number);
608 }
609 catch(Exception error) {
610 count = 0;
611 }
612 }
613 return count;
614 }
615 /** This method extracts the option list details, if any, from a certain element and stores them in an array ready to be passed as arguments to the <strong>Dictionary</strong>.
616 * @param element The <strong>Element</strong> whose option list we wish to view.
617 * @return A <strong>String[]</strong> containing the details of the option list.
618 * TODO implement.
619 * @see org.greenstone.gatherer.Dictionary
620 */
621 static final public String[] getOptionListDetails(Element element) {
622 return null;
623 }
624
625
626 /** This method extracts the structural details from a certain element and stores them in an array, all ready for passing to the <strong>Dictionary</strong>.
627 * @param element The <Strong>Element</strong> whose details we wish to gather.
628 * @return A <strong>String[]</strong> containing the structural details.
629 */
630 static final public String[] getStructuralDetails(MetadataSet mds, Element element) {
631 String details[] = new String[4];
632 //Element root = (Element)element.getParentNode();
633 //details[0] = root.getAttribute("name");
634 //details[1] = root.getAttribute("namespace");
635 details[0] = mds.getName();
636 details[1] = mds.getNamespace();
637 details[2] = getFullName(element);
638 details[3] = null;
639 // Get attributes
640 Vector attributes = new Vector();
641 for(Node n=element.getFirstChild(); n!=null; n=n.getNextSibling()) {
642 if(n.getNodeName().equals("Attribute")) {
643 Element temp = (Element)n;
644 attributes.add(temp.getAttribute("name") + "=" + getValue(n));
645 }
646 }
647 // Sort attributes
648 Collections.sort(attributes);
649 // Add attributes to details.
650 for(int i = 0; i < attributes.size(); i++) {
651 if(details[3] == null) {
652 details[3] = " " + attributes.get(i);
653 }
654 else {
655 details[3] = details[3] + "\n " + attributes.get(i);
656 }
657 }
658 return details;
659 }
660
661 /** Method to retrieve the value of a given node (not the assigned values tree!).
662 * @param element The <strong>Element</strong> whose value we wish to find.
663 * @return The value found as a <strong>String</strong>, or <i>null</i> if this element has no value.
664 */
665 static final public String getValue(Node element) {
666 // If we've been given a subject node first retrieve its value node.
667 if(element.getNodeName().equals("Subject")) {
668 element = getNodeFromNamed(element, "Value");
669 }
670 // If we've got a value node, then reconstruct the text. Remember that DOM will split text over 256 characters into several text nodes
671 if(element != null && element.hasChildNodes()) {
672 StringBuffer text_buffer = new StringBuffer();
673 NodeList text_nodes = element.getChildNodes();
674 for(int i = 0; i < text_nodes.getLength(); i++) {
675 Node possible_text = text_nodes.item(i);
676 if(possible_text.getNodeName().equals(StaticStrings.TEXT_NODE)) {
677 text_buffer.append(possible_text.getNodeValue());
678 }
679 }
680 return text_buffer.toString();
681 }
682 return "";
683 }
684
685 /** Method to traverse the given value tree, and build up a hashtable of mappings between the value path key names and the Subject nodes of the tree.
686 * @param current The root <strong>Node</strong> of a subtree of the AssignedValues tree.
687 * @param prefix The value path key <strong>String</string>, which shows the path from the root of the AssignedValue tree to <i>current</i>s parent using '\' as a separator.
688 * @param values A <strong>Hashtable</strong> containing the mapping discovered so far in our tree traversal.
689 */
690 static final private void getValueMappings(Node current, String prefix, Hashtable values) {
691 if(current != null) {
692 String name = current.getNodeName();
693 String new_prefix = prefix;
694 // If we've found the outer layer of a new value, add it to our mapping
695 if(name.equals("Subject")) {
696 Node value_node = getNodeFromNamed(current, "Value");
697 String value = getValue(value_node);
698 if(new_prefix != null) {
699 new_prefix = new_prefix + "\\" + value;
700 }
701 else {
702 new_prefix = value;
703 }
704 values.put(new_prefix, current);
705 }
706 if(name.equals("Subject") || name.equals("AssignedValues")) {
707 for(Node child = current.getFirstChild(); child != null;
708 child = child.getNextSibling()) {
709 getValueMappings(child, new_prefix, values);
710 }
711 }
712 }
713 }
714 /** Parses the value tree template file.
715 * @return The Document parsed.
716 */
717 static final public Document getValueTreeTemplate() {
718 return Utility.parse(GEMS.METADATA_VALUE_TEMPLATE, true);
719 }
720
721 /** Determine if the named attribute is language specific for this collection. This information is found in a DOM attribute of the document element, as a comma separated list of attribute names.
722 * @param document the Document for which we wish to check the language requirements
723 * @param attribute_name_str the name of the attribute we a testing as a String
724 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGEDEPENDANT_ATTRIBUTE
725 */
726 static private boolean isAttributeLanguageDependant(Document document, String attribute_name_str) {
727 String language_specific_attributes = document.getDocumentElement().getAttribute(StaticStrings.LANGUAGEDEPENDANT_ATTRIBUTE).toLowerCase();
728 return language_specific_attributes.indexOf(attribute_name_str) != -1;
729 }
730
731 /** Determine if the given document is a legacy MDS or a new multilingual one. The easiest way to tell is whether there is a language_dependant attribute in the document element.
732 * @param document the Document to test
733 * @return true if this is an old mds, false otherwise
734 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGEDEPENDANT_ATTRIBUTE
735 * @see org.greenstone.gatherer.util.StaticStrings#EMPTY_STR
736 */
737 static private boolean isLegacyMDS(Document document) {
738 ///ystem.err.println("isLegacyMDS(): l_d = " + document.getDocumentElement().getAttribute(StaticStrings.LANGUAGEDEPENDANT_ATTRIBUTE));
739 return (document.getDocumentElement().getAttribute(StaticStrings.LANGUAGEDEPENDANT_ATTRIBUTE)).equals(StaticStrings.EMPTY_STR);
740 }
741
742 /** Method to compare two OptionsLists for equality.
743 * @param al A <strong>Node</strong> which represents an OptionList.
744 * @param bl The <strong>Node</strong> we wish to test against.
745 * @return A <i>boolean</i> which is <i>true</i> if the two option lists are equal, <i>false</i> otherwise.
746 * TODO Implementation
747 */
748 static final private boolean optionListsEqual(Node al, Node bl) {
749 // Compare the 'restricted' attribute of the two lists.
750 Element ae = (Element) al;
751 Element be = (Element) bl;
752 if(!ae.getAttribute("restricted").equals
753 (be.getAttribute("restricted"))) {
754 return false;
755 }
756 // Compare the Values under each list.
757 for(Node an = al.getFirstChild(); an != null;
758 an = an.getNextSibling()){
759 if(an.getNodeName().equals("Value")) {
760 boolean matched = false;
761 for(Node bn = bl.getFirstChild(); bn != null && !matched;
762 bn = bn.getNextSibling()) {
763 if(bn.getNodeName().equals("Value")) {
764 matched = valuesEqual(an, bn);
765 }
766 }
767 if(!matched) {
768 return false;
769 }
770 }
771 }
772 return true;
773 }
774
775 /** A method to remove a specific attribute element from an element. This attribute must match in name, language and in value before being removed. Note that this method supports both legacy and multilingual optimized versions of the mds.
776 * @param element_element the Element which represent the metadata element we are altering
777 * @param attribute_name_str the name of the attribute to remove as a String
778 * @param language_code_str the language code we must match as a String
779 * @param value_str the value String which also must match before we remove anything
780 * @return true if the desired attribute was successfully found and removed, false otherwise
781 * @see org.greenstone.gatherer.msm.MSMUtils#isAttributeLanguageDependant
782 * @see org.greenstone.gatherer.msm.MSMUtils#getValue(Node)
783 * @see org.greenstone.gatherer.util.StaticStrings#ATTRIBUTE_ELEMENT
784 * @see org.greenstone.gatherer.util.StaticStrings#CODE_ATTRIBUTE
785 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ATTRIBUTE
786 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ELEMENT
787 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
788 */
789 static public boolean removeElementAttribute(Element element_element, String attribute_name_str, String language_code_str, String value_str) {
790 // Multilingual Optimized version
791 // 1. Determine the if this is one of the language specific attributes
792 if(isAttributeLanguageDependant(element_element.getOwnerDocument(), attribute_name_str)) {
793 // Retrieve the language elements, and determine the correct one
794 NodeList language_elements = element_element.getElementsByTagName(StaticStrings.LANGUAGE_ELEMENT);
795 for(int i = 0; i < language_elements.getLength(); i++) {
796 Element language_element = (Element) language_elements.item(i);
797 if(language_element.getAttribute(StaticStrings.CODE_ATTRIBUTE).equalsIgnoreCase(language_code_str)) {
798 NodeList attribute_elements = language_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
799 for(int j = 0; j < attribute_elements.getLength(); j++) {
800 Element attribute_element = (Element) attribute_elements.item(j);
801 String target_name_str = attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
802 String target_value_str = MSMUtils.getValue(attribute_element);
803 if(attribute_name_str.equals(target_name_str) && value_str.equals(target_value_str)) {
804 language_element.removeChild(attribute_element);
805 if(attribute_elements.getLength() == 0) {
806 element_element.removeChild(language_element);
807 }
808 target_value_str = null;
809 target_name_str = null;
810 attribute_element = null;
811 attribute_elements = null;
812 language_element = null;
813 language_elements = null;
814 return true;
815 }
816 target_value_str = null;
817 target_name_str = null;
818 attribute_element = null;
819 }
820 attribute_elements = null;
821 }
822 language_element = null;
823 }
824 language_elements = null;
825 // Not found
826 return false;
827 }
828 // Otherwise just use the old method
829
830 // Find the attribute to remove
831 NodeList attribute_elements = element_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
832 for (int k = 0; k < attribute_elements.getLength(); k++) {
833 Element attribute_element = (Element) attribute_elements.item(k);
834 // Remember to ignore any attributes that live within nested language elements
835 if (attribute_element.getParentNode() == element_element && attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equals(attribute_name_str) && attribute_element.getAttribute(StaticStrings.LANGUAGE_ATTRIBUTE).equalsIgnoreCase(language_code_str) && MSMUtils.getValue(attribute_element).equals(value_str)) {
836 // Match found, so remove the attribute node and return
837 element_element.removeChild(attribute_element);
838 attribute_element = null;
839 attribute_elements = null;
840 return true;
841 }
842 attribute_element = null;
843 }
844 attribute_elements = null;
845 // No match found
846 return false;
847 }
848
849 /** can only be used to set the english value */
850 static final public void setIdentifier(Node element, String value) {
851 // Get the 'identifier' Element
852 for(Node node = element.getFirstChild(); node != null;
853 node = node.getNextSibling()) {
854 if(node.getNodeName().equals("Attribute")) {
855 Element target = (Element)node;
856 if(target.getAttribute("name").equals("identifier") && target.getAttribute("language").equals("en")) {
857 Node text = target.getFirstChild();
858 text.setNodeValue(value);
859 break;
860 }
861 }
862 }
863 }
864 /** Set the value of the element attribute occurances.
865 * @param element The <strong>Element</strong> to change.
866 * @param value The value to change by as an <i>int</i>.
867 */
868 static final public void setOccurance(Element element, int value) {
869 Integer new_value = new Integer(getOccurances(element) + value);
870 element.setAttribute("occurances", new_value.toString());
871 }
872
873 /** Set the #text node value of some element.
874 * @param element the Element whose value we wish to set
875 * @param value the new value for the element as a String
876 */
877 static final public void setValue(Element element, String value) {
878 // Remove any existing child node(s)
879 clear(element);
880 // Add new text node.
881 if (value != null) {
882 element.appendChild(element.getOwnerDocument().createTextNode(value));
883 }
884 }
885
886 /** This method also traverses the tree, but this one is used to gather all the values and aliases at once, and to 'prune' the tree if necessary.
887 * @param current The current root <strong>Node</strong> of this AssignedValues tree subtree.
888 * @param return_filter This <i>int</i> specifies what nodes from the tree should be returned, where;<br>VALUES = return values only<br>ALIASES = return aliases only<br>BOTH = return both values and aliases<br>NONE = return nothing.
889 * @param remove_leaves A leaf node is a subject that contains no child subjects. If this <i>boolean</i> is set to <i>true</i> then the leaf nodes will be removed from the tree.
890 * @return A <strong>Node[]</strong> containing whatever values or aliases have been found during the trees traversal.
891 */
892 static final public Node[] traverseTree(Node current, int return_filter, boolean remove_leaves) {
893 Node leaves[] = null;
894 String name = current.getNodeName();
895 if(name.equals("Value") && (return_filter == VALUES || return_filter == BOTH)) {
896 leaves = ArrayTools.add(leaves, current);
897 }
898 else if(name.equals("Alias") && (return_filter == ALIASES || return_filter == BOTH)) {
899 leaves = ArrayTools.add(leaves, current);
900 }
901 else if(name.equals("Subject")) {
902 boolean has_subject_child = false;
903 Node children[] = ArrayTools.nodeListToNodeArray(current.getChildNodes());
904 for(int i = 0; i < children.length; i++) {
905 if(children[i].getNodeName().equals("Subject")) {
906 has_subject_child = true;
907 }
908 leaves = ArrayTools.add(leaves, traverseTree(children[i], return_filter, remove_leaves));
909 }
910 if(!has_subject_child && remove_leaves) {
911 Node parent = current.getParentNode();
912 parent.removeChild(current);
913 }
914 }
915 else if(name.equals("AssignedValues")) {
916 Node children[] = ArrayTools.nodeListToNodeArray(current.getChildNodes());
917 for(int i = 0; i < children.length; i++) {
918 leaves = ArrayTools.add(leaves, traverseTree(children[i], return_filter, remove_leaves));
919 }
920 }
921 return leaves;
922 }
923
924 /** This method is used to systematically merge two AssignedValues tree. Both trees have their current values mapped, then the new tree is searched for key paths that don't exist in the current tree. If such a key is found, the Subject <strong>Node</strong> it maps to is retrieved and then imported and added to whatever was the closest available node (in terms of tree path) in the current tree.
925 * @param a_set The MetadataSet from which the Element a came from.
926 * @param a The Element at the root of the current AssignedValues tree.
927 * @param b_set The MetadataSet from which the Element b came from.
928 * @param b The root Element of the tree that is being merged.
929 * @return A <i>boolean</i> which is <i>true</i> if the trees merged without error, <i>false</i> otherwise.
930 */
931 static final public boolean updateValueTree(MetadataSet a_set, Element a, MetadataSet b_set, Element b) {
932 GValueModel avt = a_set.getValueTree(new ElementWrapper(a));
933 GValueModel bvt = b_set.getValueTree(new ElementWrapper(b));
934 // If neither element even has a value tree, we're all done.
935 if(avt == null && bvt == null) {
936 avt = null;
937 bvt = null;
938 return true;
939 }
940 // If the new element has no value tree then nothing needs to be done.
941 else if(avt != null && bvt == null) {
942 avt = null;
943 bvt = null;
944 return true;
945 }
946 // If only the new element has a value tree, then add all of its values
947 // immediately.
948 else if(avt == null && bvt != null) {
949 a_set.addValueTree(new ElementWrapper(a), bvt);
950 avt = null;
951 bvt = null;
952 return true;
953 }
954 // We have both trees for both elements, time to merge.
955 else {
956 Document document = avt.getDocument();
957 Hashtable a_map = new Hashtable();
958 getValueMappings(document.getDocumentElement(), null, a_map);
959 Hashtable b_map = new Hashtable();
960 getValueMappings(bvt.getDocument().getDocumentElement(), null, b_map);
961 // For each new entry in b_map
962 for(Enumeration b_keys = b_map.keys(); b_keys.hasMoreElements(); ) {
963 String b_key = (String)b_keys.nextElement();
964 // Test if there is already an entry in a_map.
965 if(!a_map.containsKey(b_key)) {
966 // If not, search through a_map for the longest match.
967 Node target = document.getDocumentElement();
968 String last_match = null;
969 for(Enumeration a_keys = a_map.keys();
970 a_keys.hasMoreElements(); ) {
971 String a_key = (String)a_keys.nextElement();
972 if(b_key.startsWith(a_key)) {
973 if(last_match == null || a_key.length() > last_match.length()) {
974 last_match = a_key;
975 target = (Node)a_map.get(a_key);
976 }
977 }
978 a_key = null;
979 }
980 // Now import the node at b_key and add it to target.
981 Node subtree = (Node)b_map.get(b_key);
982 subtree = document.importNode(subtree, true);
983 // Find the node to insert before...
984 String name = getValue(subtree);
985 Node move = null;
986 for(Node n = target.getFirstChild(); n != null && move == null; n = n.getNextSibling()) {
987 if(n.getNodeName().equals("Subject")) {
988 if(name.compareTo(getValue(n)) <= 0) {
989 move = n;
990 }
991 }
992 }
993 if(move == null) {
994 target.appendChild(subtree);
995 }
996 else {
997 target.insertBefore(subtree, move);
998 }
999 target = null;
1000 last_match = null;
1001 subtree = null;
1002 name = null;
1003 move = null;
1004 }
1005 b_key = null;
1006 }
1007 document = null;
1008 a_map = null;
1009 b_map = null;
1010 avt = null;
1011 bvt = null;
1012 return true;
1013 }
1014 }
1015
1016 /** Method to determine if two Value nodes are equal.
1017 * @param av A <strong>Node</strong> representing a value.
1018 * @param bv The <strong>Node</strong> we want to compare it to.
1019 * @return A <i>boolean</i> which is <i>true</i> if the two value nodes are equal, <i>false</i> otherwise.
1020 */
1021 static final private boolean valuesEqual(Node av, Node bv) {
1022 // Check we are comparing apples and apples...
1023 if(av.getNodeName().equals("Value") &&
1024 bv.getNodeName().equals("Value")) {
1025 // Retrieve and then compare their text values.
1026 Node at = av.getFirstChild();
1027 Node bt = bv.getFirstChild();
1028 if(at.getNodeValue().equals(bt.getNodeValue())) {
1029 return true;
1030 }
1031 }
1032 return false;
1033 }
1034
1035 /** A comparator for sorting metadata element-value pairs into their standard order (elements) then alphabetical order (values). */
1036// static final private class MetadataComparator
1037// implements Comparator {
1038// /** Compares its two arguments for order. */
1039// public int compare(Object o1, Object o2) {
1040// int result = 0;
1041// ElementWrapper e1 = null;
1042// ElementWrapper e2 = null;
1043// String v1 = null;
1044// String v2 = null;
1045// if(o1 instanceof Metadata && o2 instanceof Metadata) {
1046// Metadata m1 = (Metadata) o1;
1047// Metadata m2 = (Metadata) o2;
1048// ///ystem.err.println("MSMUtils.compare(" + m1 + ", " + m2 + ") = ");
1049// e1 = m1.getElement();
1050// e2 = m2.getElement();
1051// v1 = m1.getValue().toLowerCase();
1052// v2 = m2.getValue().toLowerCase();
1053// }
1054// else if(o1 instanceof ElementWrapper && o2 instanceof ElementWrapper) {
1055// e1 = (ElementWrapper) o1;
1056// e2 = (ElementWrapper) o2;
1057// }
1058// if(e1 != null && e2 != null) {
1059// // First we compare the namespaces
1060// result = e1.getNamespace().compareTo(e2.getNamespace());
1061// if(result == 0 && Gatherer.c_man != null && Gatherer.c_man.ready() && e1.getNamespace() != null) {
1062// // Now, given both elements are in the same set, we compare the element ordering using methods in MetadataSet
1063// MetadataSet set = Gatherer.c_man.getCollection().msm.getSet(e1.getNamespace());
1064// ///ystem.err.print("MetadataSet.compare(" + e1 + ", " + e2 + ") = ");
1065// if(set != null) {
1066// result = set.compare(e1.getElement(), e2.getElement());
1067// ///ystem.err.println(result);
1068// if(result == 0 && v1 != null && v2 != null) {
1069// // Finally we compare the values alphabetically.
1070// result = v1.compareTo(v2);
1071// }
1072// }
1073// else {
1074// return 0;
1075// }
1076// }
1077// }
1078// else {
1079// result = o1.toString().compareTo(o2.toString());
1080// }
1081// ///ystem.err.println("Result: " + result);
1082// return result;
1083// }
1084
1085// /** Indicates whether some other object is "equal to" this Comparator. */
1086// public boolean equals(Object obj) {
1087// return compare(this, obj) == 0;
1088// }
1089// }
1090}
Note: See TracBrowser for help on using the repository browser.