1 /*
2  * @(#)HelpUtilities.java	1.56 06/10/30
3  *
4  * Copyright (c) 2006 Sun Microsystems, Inc.  All Rights Reserved.
5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
6  *
7  * This code is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License version 2 only, as
9  * published by the Free Software Foundation.  Sun designates this
10  * particular file as subject to the "Classpath" exception as provided
11  * by Sun in the LICENSE file that accompanied this code.
12  *
13  * This code is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16  * version 2 for more details (a copy is included in the LICENSE file that
17  * accompanied this code).
18  *
19  * You should have received a copy of the GNU General Public License version
20  * 2 along with this work; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22  *
23  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
24  * CA 95054 USA or visit www.sun.com if you need additional information or
25  * have any questions.
26  */
27 
28 package javax.help;
29 
30 import java.net.URL;
31 
32 import java.io.InputStream;
33 
34 import java.util.Enumeration;
35 import java.util.Hashtable;
36 import java.util.Locale;
37 import java.util.MissingResourceException;
38 import java.util.ResourceBundle;
39 import java.util.Vector;
40 
41 import java.beans.BeanInfo;
42 import java.beans.Introspector;
43 
44 import java.text.CollationElementIterator;
45 import java.text.Collator;
46 import java.text.MessageFormat;
47 import java.text.RuleBasedCollator;
48 
49 import java.awt.Component;
50 import java.awt.IllegalComponentStateException;
51 
52 
53 /**
54  * Provides a number of utility functions:
55  *
56  * Support for Beans, mapping from a Bean class to its HelpSet and to
57  * its ID.
58  * Support for finding localized resources.
59  * Support for getting the default Query Engine
60  *
61  * This class has no public constructor.
62  *
63  * @author Eduardo Pelegri-Llopart
64  * @author Roger D. Brinkley
65  * @version	1.56	10/30/06
66  */
67 
68 public class HelpUtilities {
69 
70     //=========
71 
72     /**
73      * Beans support
74      */
75 
76     /**
77      * Given the class for a bean, get its HelpSet.
78      * Returns the helpSetName property of the BeanDescriptor if defined.
79      * Otherwise it returns a name as follows:
80      * If the class is in the unnamed package, it returns <em>beanClassName</em>Help.hs.
81      * Otherwise if it's in the form <em>package.ClassName</em> it returns
82      * <em>package</em>/Help.hs after replacing "." with "/" in <em>package</em>.
83      *
84      * @param beanClass The Class
85      * @return A String with the name of the HelpSet
86      */
87 
getHelpSetNameFromBean(Class beanClass)88     public static String getHelpSetNameFromBean(Class beanClass) {
89 	String helpSetName;
90 	try {
91 	    BeanInfo bi = Introspector.getBeanInfo(beanClass);
92 	    helpSetName =
93 		(String) bi.getBeanDescriptor().getValue("helpSetName");
94 	} catch (Exception ex) {
95 	    helpSetName = null;
96 	}
97 	if (helpSetName == null) {
98 	    String className = beanClass.getName();
99 	    int index = className.lastIndexOf(".");
100 	    if (index == -1) {
101 		// unnamed package
102 		helpSetName = className + "Help.hs";
103 	    } else {
104 		String packageName = className.substring(0, index);
105 		helpSetName = packageName.replace('.','/') + "/Help.hs";
106 	    }
107 	}
108 	return helpSetName;
109     }
110 
111     /**
112      * Given the class for a bean, get its ID string.
113      * Returns the helpID property of the BeanDescriptor if defined,
114      * otherwise it returns <em>beanName</em>.topID.
115      *
116      * @param beanClass the Class
117      * @return A String with the ID to use
118      */
119 
getIDStringFromBean(Class beanClass)120     public static String getIDStringFromBean(Class beanClass) {
121 	String helpID;
122 	try {
123 	    BeanInfo bi = Introspector.getBeanInfo(beanClass);
124 	    helpID = (String) bi.getBeanDescriptor().getValue("helpID");
125 	} catch (Exception ex) {
126 	    helpID = null;
127 	}
128 	if (helpID == null) {
129 	    String className = beanClass.getName();
130 	    helpID = className + ".topID";
131 	}
132 	return helpID;
133     }
134 
135 
136     //=======
137     /**
138      * Default for the search engine
139      */
getDefaultQueryEngine()140     public static String getDefaultQueryEngine() {
141 	return "com.sun.java.help.search.DefaultSearchEngine";
142     }
143 
144     //========
145 
146     /**
147      * I18N/ L10N support
148      */
149 
150     /**
151      * Search for a helpset file following the standard conventions
152      * used in ResourceBundle.
153      */
154 
155     /**
156      * Locate a resource relative to a given classloader CL.
157      * The name of the resource is composed by using FRONT,
158      * adding _LANG _COUNTRY _VARIANT (with the usual rules)
159      * and ending with BACK, which will usually be an extension
160      * like ".hs" for a HelpSet, or ".class" for a class
161      *
162      * This method is a convenience method for getLocalizedResource() with
163      * a tryRead parameter set to false.
164      *
165      * This functionality should likely be exposed as part of JDK1.2
166      * @param cl The ClassLoader to get the resource from. If cl is null the default
167      * ClassLoader is used.
168      * @returns the URL to the localized resource or null if not found.
169      */
getLocalizedResource(ClassLoader cl, String front, String back, Locale locale)170     public static URL getLocalizedResource(ClassLoader cl,
171 					   String front,
172 					   String back,
173 					   Locale locale) {
174 	return getLocalizedResource(cl, front, back, locale, false);
175     }
176 
177     /**
178      * Locate a resource relative to a given classloader CL.
179      * The name of the resource is composed by using FRONT,
180      * adding _LANG _COUNTRY _VARIANT (with the usual rules)
181      * and ending with BACK, which is usually an extension
182      * like ".hs" for a HelpSet, or ".class" for a class
183      *
184      * This version accepts an explicit argument to work around some browser bugs.
185      *
186      * This functionality should likely be exposed as part of JDK1.2
187      * @param cl The ClassLoader to get the resource from. If cl is null the default
188      * ClassLoader is used.
189      * @returns the URL to the localized resource or null if not found.
190      */
getLocalizedResource(ClassLoader cl, String front, String back, Locale locale, boolean tryRead)191     public static URL getLocalizedResource(ClassLoader cl,
192 					   String front,
193 					   String back,
194 					   Locale locale,
195 					   boolean tryRead) {
196 	URL url;
197 	for (Enumeration tails = getCandidates(locale);
198 	     tails.hasMoreElements(); ) {
199 	    String tail = (String) tails.nextElement();
200 	    String name	= (new StringBuffer(front)).append(tail).append(back).toString();
201 	    if (cl == null) {
202 		url = ClassLoader.getSystemResource(name);
203 	    } else {
204 		url = cl.getResource(name);
205 	    }
206 	    if (url != null) {
207 		if (tryRead) {
208 		    // Try doing an actual read to be sure it exists
209 		    try {
210 			InputStream is = url.openConnection().getInputStream();
211 			if (is != null) {
212 			    int i = is.read();
213 			    is.close();
214 			    if (i != -1) {
215 				return url;
216 			    }
217 			}
218 		    } catch (Throwable t) {
219 		        // ignore and continue looking
220 		    }
221 		} else {
222 		    return url;
223 		}
224 	    }
225 	}
226 	return null;
227     }
228 
229     private static Hashtable tailsPerLocales = new Hashtable();
230 
231     /**
232      * This returns an enumeration of String tails.
233      *
234      * The core functionality on which getLocalizedResource is based.
235      *
236      * The suffixes are based on (1) the desired locale and (2) the default locale
237      * in the following order from lower-level (more specific) to parent-level
238      * (less specific):
239      * <p>  "_" + language1 + "_" + country1 + "_" + variant1
240      * <BR> "_" + language1 + "_" + country1
241      * <BR> "_" + language1
242      * <BR> ""
243      * <BR> "_" + language2 + "_" + country2 + "_" + variant2
244      * <BR> "_" + language2 + "_" + country2
245      * <BR> "_" + language2
246      *
247      * The enumeration is of StringBuffer.
248      * We pay some attention to efficiency in case a method like this is promoted,
249      * hence we cache per locale.
250      */
251 
getCandidates(Locale locale)252     public static synchronized Enumeration getCandidates(Locale locale) {
253 	Vector tails;
254 	LocalePair pair = new LocalePair(locale, Locale.getDefault());
255 	tails = (Vector) tailsPerLocales.get(pair);
256 	if (tails != null) {
257 	    debug("getCandidates - cached copy");
258 	    return tails.elements();
259 	}
260 
261 	String lname1 = locale.toString();
262 	StringBuffer name1 = new StringBuffer("_").append(lname1);
263 	if (lname1 == null) {
264 	    name1.setLength(0);
265 	}
266 	tails = new Vector();
267 	// Now add them to the vector
268 	while (name1.length() != 0) {
269 	    debug("  adding ", name1);
270 	    String s = name1.toString();
271 	    tails.addElement(s);
272 	    int lastUnder = s.lastIndexOf('_');
273 	    if (lastUnder != -1) {
274 		name1.setLength(lastUnder);
275 	    }
276 	}
277 	debug("  addign -- null -- ");
278 	tails.addElement("");
279 	if (locale != Locale.getDefault()) {
280 	    // Otherwise, no need to search twice
281 	    String lname2 = Locale.getDefault().toString();
282 	    StringBuffer name2 = new StringBuffer("_").append(lname2);
283 	    if (lname2 == null) {
284 		name2.setLength(0);
285 	    }
286 	    while (name2.length() != 0) {
287 		debug("  adding ", name2);
288 		String s = name2.toString();
289 		tails.addElement(s);
290 		int lastUnder = s.lastIndexOf('_');
291 		if (lastUnder != -1) {
292 		    name2.setLength(lastUnder);
293 		}
294 	    }
295 	}
296 	tailsPerLocales.put(pair, tails);
297 	debug("tails is == ", tails);
298 	return tails.elements();
299     }
300 
301     /**
302      * Auxiliary class used in dealing with locale.
303      */
304 
305     static class LocalePair {
LocalePair(Locale locale1, Locale locale2)306 	LocalePair(Locale locale1, Locale locale2) {
307 	    this.locale1 = locale1;
308 	    this.locale2 = locale2;
309 	}
310 
hashCode()311 	public int hashCode() {
312 	    return locale1.hashCode() + locale2.hashCode();
313 	}
314 
equals(Object obj)315 	public boolean equals(Object obj) {
316 	    if (obj == null || ! (obj instanceof LocalePair)) {
317 		return false;
318 	    } else {
319 		LocalePair p = (LocalePair) obj;
320 		return locale1.equals(p.locale1) && locale2.equals(p.locale2);
321 	    }
322 	}
323 
324 	Locale locale1;
325 	Locale locale2;
326     }
327 
328     // ==========
329 
330     /**
331      * Get a localized Error String
332      */
333     private static Hashtable bundles;
334     private static ResourceBundle lastBundle = null;
335     private static Locale lastLocale = null;
336 
337     /**
338      * Gets the locale of a component. If the component is null
339      * it returns the defaultLocale. If the call to component.getLocale
340      * returns an IllegalComponentStateException, the defaultLocale is
341      * returned.
342      */
getLocale(Component c)343     public static Locale getLocale(Component c) {
344 	if (c == null) {
345 	    return Locale.getDefault();
346 	}
347 	try {
348 	    return c.getLocale();
349 	} catch (IllegalComponentStateException ex) {
350 	    return Locale.getDefault();
351 	}
352 
353     }
354 
getBundle(Locale l)355     static synchronized private ResourceBundle getBundle(Locale l) {
356 	if (lastLocale == l) {
357 	    return lastBundle;
358 	}
359 	if (bundles == null) {
360 	    bundles = new Hashtable();
361 	}
362 	ResourceBundle back = (ResourceBundle) bundles.get(l);
363 	if (back == null) {
364 	    try {
365 		back = ResourceBundle.getBundle("javax.help.resources.Constants",
366 						l);
367 	    } catch (MissingResourceException ex) {
368 		throw new Error("Fatal: Resource for javahelp is missing");
369 	    }
370 	    bundles.put(l, back);
371 	}
372 	lastBundle = back;
373 	lastLocale = l;
374 	return back;
375     }
376 
377 
378     /**
379      * Get the Text message for the default locale.
380      *
381      * The getString version does not involve a format.
382      */
getString(String key)383     static public String getString(String key) {
384 	return getString(Locale.getDefault(), key);
385     }
386 
getText(String key)387     static public String getText(String key) {
388 	return getText(Locale.getDefault(), key, null, null);
389     }
390 
391     /**
392      * @param s1 The first parameter of a string. A null is valid for s1.
393      */
getText(String key, String s1)394     static public String getText(String key, String s1) {
395 	return getText(Locale.getDefault(), key, s1, null);
396     }
397 
398     /**
399      * @param s1 The first parameter of a string. A null is valid for s1.
400      * @param s2 The first parameter of a string. A null is valid for s2.
401      */
getText(String key, String s1, String s2)402     static public String getText(String key, String s1, String s2) {
403 	return getText(Locale.getDefault(), key, s1, s2);
404     }
405 
406     /**
407      * @param s1 The first parameter of a string. A null is valid for s1.
408      * @param s2 The first parameter of a string. A null is valid for s2.
409      * @param s3 The first parameter of a string. A null is valid for s3.
410      */
getText(String key, String s1, String s2, String s3)411     static public String getText(String key, String s1, String s2, String s3) {
412 	return getText(Locale.getDefault(), key, s1, s2, s3);
413     }
414 
415     /**
416      * Versions with an explicit locale.
417 */
getString(Locale l, String key)418     static public String getString(Locale l, String key) {
419 	ResourceBundle bundle = getBundle(l);
420 	try {
421 	    return bundle.getString(key);
422 	} catch (MissingResourceException ex) {
423 	    throw new Error("Fatal: Localization data for JavaHelp is broken.  Missing "+key+" key.");
424 	}
425     }
426 
getStringArray(Locale l, String key)427     static public String[] getStringArray(Locale l, String key) {
428 	ResourceBundle bundle = getBundle(l);
429 	try {
430 	    return bundle.getStringArray(key);
431 	} catch (MissingResourceException ex) {
432 	    throw new Error("Fatal: Localization data for JavaHelp is broken.  Missing "+key+" key.");
433 	}
434     }
435 
436     /**
437      * @param s1 The first parameter of a string. A null is valid for s1.
438      * @param s2 The first parameter of a string. A null is valid for s2.
439      * @param s3 The first parameter of a string. A null is valid for s3.
440      */
getText(Locale l, String key)441     static public String getText(Locale l, String key) {
442 	return getText(l, key, null, null, null);
443     }
444 
445     /**
446      * @param s1 The first parameter of a string. A null is valid for s1.
447      */
getText(Locale l, String key, String s1)448     static public String getText(Locale l, String key, String s1) {
449 	return getText(l, key, s1, null, null);
450     }
451 
452     /**
453      * @param s1 The first parameter of a string. A null is valid for s1.
454      * @param s2 The first parameter of a string. A null is valid for s2.
455      */
getText(Locale l, String key, String s1, String s2)456     static public String getText(Locale l, String key, String s1, String s2) {
457 	return getText(l, key, s1, s2, null);
458     }
459 
460     /**
461      * @param s1 The first parameter of a string. A null is valid for s1.
462      * @param s2 The first parameter of a string. A null is valid for s2.
463      * @param s3 The first parameter of a string. A null is valid for s3.
464      */
getText(Locale l, String key, String s1, String s2, String s3)465     static public String getText(Locale l, String key,
466 				 String s1, String s2, String s3) {
467 	ResourceBundle bundle = getBundle(l);
468 	// workaround bugs in JDK 1.1.6
469 	// This mimic's what JDK 1.2 will do.
470 	if (s1 == null) {
471 	    s1 = "null";
472 	}
473 	if (s2 == null) {
474 	    s2 = "null";
475 	}
476 	if (s3 == null) {
477 	    s3 = "null";
478 	}
479 	// end workaround
480 	try {
481 	    String fmt = bundle.getString(key);
482 	    String[] args = {s1, s2, s3};
483 	    MessageFormat format = new MessageFormat(fmt);
484 	    // workaround a bug in MessageFormat.setLocale
485 	    try {
486 		format.setLocale(l);
487 	    } catch (NullPointerException ee) {
488 	    }
489 	    return format.format(args);
490 	} catch (MissingResourceException ex) {
491 	    throw new Error("Fatal: Localization data for JavaHelp is broken.  Missing "+key+" key.");
492 	}
493     }
494 
495     /**
496      * Convenient method for creating a locale from a <tt>lang</tt> string.
497      * Takes the <tt>lang</tt> string in the form of "language_country_variant"
498      * or "language-country-variant" and
499      * parses the string and creates an appropriate locale.
500      * @param lang A String representation of a locale, with the language,
501      * country and variant separated by underbars. Language is always lower
502      * case, and country is always upper case. If the language is missing the
503      * String begins with an underbar. If both language and country fields are
504      * missing, a null Locale is returned. If lang is null a null Locale is
505      * returned
506      * @returns The locale based on the string or null if the Locale could
507      * not be constructed or lang was null.
508      */
localeFromLang(String lang)509     public static Locale localeFromLang(String lang) {
510 	String language;
511 	String country;
512 	String variant=null;
513 	Locale newlocale=null;
514 	if (lang == null) {
515 	    return newlocale;
516 	}
517 	int lpt = lang.indexOf("_");
518 	int lpt2 = lang.indexOf("-");
519 	if (lpt == -1 && lpt2 == -1) {
520 	    language = lang;
521 	    country = "";
522 	    newlocale = new Locale(language, country);
523 	} else {
524 	    if (lpt == -1 && lpt2 != -1) {
525 		lpt = lpt2;
526 	    }
527 	    language = lang.substring(0, lpt);
528 	    int cpt = lang.indexOf("_", lpt+1);
529 	    int cpt2 = lang.indexOf("-", lpt+1);
530 	    if (cpt == -1 && cpt2 == -1) {
531 		country = lang.substring(lpt+1);
532 		newlocale = new Locale(language, country);
533 	    } else {
534 		if (cpt == -1 && cpt2 != -1) {
535 		    cpt = cpt2;
536 		}
537 		country = lang.substring(lpt+1, cpt);
538 		variant = lang.substring(cpt+1);
539 		newlocale = new Locale(language, country, variant);
540 	    }
541 	}
542 	return newlocale;
543     }
544 
545     /**
546      * Returns information about whether a string is
547      * contained in another string. Compares the character data stored in two
548      * different strings based on the collation rules.
549      */
isStringInString(RuleBasedCollator rbc, String source, String target)550     public static boolean isStringInString(RuleBasedCollator rbc,
551 					   String source,
552 					   String target) {
553         // The basic algorithm here is that we use CollationElementIterators
554         // to step through both the source and target strings.  We compare each
555         // collation element in the source string against the corresponding one
556         // in the target, checking for differences.
557         //
558         // If a difference is found, we set <result> to false.
559         //
560         // However, it's not that simple.  If we find a tertiary difference
561         // (e.g. 'A' vs. 'a') near the beginning of a string, it can be
562         // overridden by a primary difference (e.g. "A" vs. "B") later in
563         // the string.  For example, "AA" < "aB", even though 'A' > 'a'.
564         //
565         // To keep track of this, we use strengthResult to keep track of the
566         // strength of the most significant difference that has been found
567         // so far.  When we find a difference whose strength is greater than
568         // strengthResult, it overrides the last difference (if any) that
569         // was found.
570 
571 	debug("isStringInString source=" + source + " targe =" + target);
572 
573 	// Initial sanity checks
574 	if (source == null || target == null) {
575 	    return false;
576 	}
577 	if (source.length() == 0 && target.length() == 0) {
578 	    return true;
579 	}
580 
581 	// on to the real code
582         int strengthResult = Collator.IDENTICAL;
583         CollationElementIterator sourceCursor, targetCursor;
584 	boolean isFrenchSec = false;
585 
586 	// set up the RuleBasedCollator and extract
587 	// various attributes
588 	rbc.setDecomposition(Collator.FULL_DECOMPOSITION);
589 	String rules = rbc.getRules();
590 	if (rules.startsWith("@")) {
591 	    // I suspect we are cheating here when multiple rules are merged
592 	    // One would think you could get this attribute but you can't
593 	    isFrenchSec = true;
594 	}
595 
596 	// Get the CollationElementIterators for the source and target
597 	sourceCursor = rbc.getCollationElementIterator(source);
598 	targetCursor = rbc.getCollationElementIterator(target);
599 
600 	// Various locale specific matching parameters
601         int sOrder = 0, tOrder = 0;
602 	int pSOrder, pTOrder;
603 	short secSOrder, secTOrder;
604 	short terSOrder, terTOrder;
605         boolean gets = true, gett = true;
606 	int toffset = 0;
607         boolean initialCheckSecTer, checkSecTer, checkTertiary;
608 
609    startSearchForFirstMatch:
610         while(true) {
611 	    debug("while(true) toffset=" + toffset);
612 	    // Set the starting point for this comparison
613 	    try {
614 		sourceCursor.setOffset(0);
615 	    } catch (NoSuchMethodError ex3) {
616 		// ingnore it
617 	    }
618 	    sOrder = sourceCursor.next();
619 	    try {
620 		targetCursor.setOffset(toffset);
621 	    } catch (NoSuchMethodError ex4) {
622 		// Can't really do anything here. Return false.
623 	    } catch (Exception e) {
624 		// We can get an exception when the toffset is really the
625 		// end of string
626 		// Just catch it and return false;
627 		return false;
628 	    }
629 	    tOrder = targetCursor.next();
630 
631 	    if (tOrder == CollationElementIterator.NULLORDER) {
632 		break;
633 	    }
634 
635 	    debug("sOrder=" + sOrder + " tOrder=" + tOrder);
636 
637 	    //Find the first character in the target that matches the first
638 	    // character in the source. Look for a complete match of
639 	    // primary, secondary and tertiary. Lacking that look for
640 	    // a match of just primary.
641 	    while (tOrder != CollationElementIterator.NULLORDER) {
642 		if (sOrder == tOrder) {
643 		    try {
644 			toffset = targetCursor.getOffset();
645 		    } catch (NoSuchMethodError ex) {
646 			//ignore it
647 		    }
648 		    break;
649 		}
650 		pSOrder = CollationElementIterator.primaryOrder(sOrder);
651 		pTOrder = CollationElementIterator.primaryOrder(tOrder);
652 		if (pSOrder == pTOrder) {
653 		    try {
654 			toffset = targetCursor.getOffset();
655 		    } catch (NoSuchMethodError ex2) {
656 			//ignore it
657 		    }
658 		    break;
659 		}
660 		tOrder = targetCursor.next();
661 		debug("next tOrder=" + tOrder);
662 	    }
663 
664 	    // If at end there is no match
665 	    if (tOrder == CollationElementIterator.NULLORDER) {
666 		return false;
667 	    }
668 
669 	    gets = false;
670 	    gett = false;
671 	    initialCheckSecTer = rbc.getStrength() >= Collator.SECONDARY;
672 	    checkSecTer = initialCheckSecTer;
673 	    checkTertiary = rbc.getStrength() >= Collator.TERTIARY;
674 
675 	    while (true) {
676 		// Get the next collation element in each of the strings, unless
677 		// we've been requested to skip it.
678 		if (gets) {
679 		    sOrder = sourceCursor.next();
680 		} else {
681 		    gets = true;
682 		}
683 		if (gett) {
684 		    tOrder = targetCursor.next();
685 		} else {
686 		    gett = true;
687 		}
688 
689 		// If we've hit the end of one of the strings, jump out of the loop
690 		if ((sOrder == CollationElementIterator.NULLORDER)||
691 		    (tOrder == CollationElementIterator.NULLORDER)) {
692 		    debug("One string at end");
693 		    break;
694 		}
695 
696 		pSOrder = CollationElementIterator.primaryOrder(sOrder);
697 		pTOrder = CollationElementIterator.primaryOrder(tOrder);
698 
699             // If there's no difference at this position, we can skip it
700 		if (sOrder == tOrder) {
701 		    if (isFrenchSec && pSOrder != 0) {
702 			if (!checkSecTer) {
703 			    // in french, a secondary difference more to the
704 			    // right is stronger,
705 			    // so accents have to be checked with each base
706 			    // element
707 			    checkSecTer = initialCheckSecTer;
708 			    // but tertiary differences are less important
709 			    // than the first
710 			    // secondary difference, so checking tertiary
711 			    // remains disabled
712 			    checkTertiary = false;
713 			}
714 		    }
715 		    debug("No diff at this positon continue");
716 		    continue;
717 		}
718 
719 		// Compare primary differences first.
720 		if ( pSOrder != pTOrder ) {
721 		    if (sOrder == 0) {
722 			// The entire source element is ignorable.
723 			// Skip to the next source element,
724 			// but don't fetch another target element.
725 			gett = false;
726 			continue;
727 		    }
728 		    if (tOrder == 0) {
729 			// The entire target element is ignorable.
730 			// Skip to the next target element,
731 			// but don't fetch another source element.
732 			gets = false;
733 			continue;
734 		    }
735 
736 		    // The source and target elements aren't ignorable,
737 		    // but it's still possible
738 		    // for the primary component of one of the elements
739 		    // to be ignorable....
740 
741 		    if (pSOrder == 0) {
742 			// The source's primary is ignorable,
743 			// but the target's isn't.  We treat ignorables
744 			// as a secondary differenc
745 			if (checkSecTer) {
746 			    // (strength is SECONDARY)
747 			    targetCursor.next();
748 			    toffset = targetCursor.getOffset();
749 			    debug("Strength is secondary pSOrder === 0");
750 			    continue startSearchForFirstMatch;
751 			}
752 			// Skip to the next source element,
753 			// but don't fetch another target element.
754 			gett = false;
755 		    } else if (pTOrder == 0) {
756 			// record differences - see the comment above.
757 			if (checkSecTer) {
758 			    // (stength is SECONDARY)
759 			    targetCursor.next();
760 			    toffset = targetCursor.getOffset();
761 			    debug("Strength is secondary - pTOrder == 0");
762 			    continue startSearchForFirstMatch;
763 			}
764 			// Skip to the next source element,
765 			// but don't fetch another target element.
766 			gets = false;
767 		    } else {
768 			// Neither of the orders is ignorable,
769 			// and we already know that the primary
770 			// orders are different because of the
771 			// (pSOrder != pTOrder) test above.
772 			// Record the difference and stop the
773 			// comparison.
774 			targetCursor.next();
775 			toffset = targetCursor.getOffset();
776 			debug("Order are ignorable");
777 			continue startSearchForFirstMatch;
778 		    }
779 		} else { // else of if ( pSOrder != pTOrder )
780 		    // primary order is the same, but complete order is
781 		    // different. So there
782 		    // are no base elements at this point, only
783 		    // ignorables (Since the strings are
784 		    // normalized)
785 
786 		    if (checkSecTer) {
787 			// a secondary or tertiary difference may
788 			// still matter
789 			secSOrder = CollationElementIterator.secondaryOrder(sOrder);
790 			secTOrder = CollationElementIterator.secondaryOrder(tOrder);
791 			if (secSOrder != secTOrder) {
792 			    // there is a secondary difference
793 			    targetCursor.next();
794 			    toffset = targetCursor.getOffset();
795 			    debug("Secondary Difference");
796 			    continue startSearchForFirstMatch;
797 			    // (strength is SECONDARY)
798 			    // (even in french, only the first
799 			    // secondary difference within
800 			    // a base character matters)
801 			} else {
802 			    if (checkTertiary) {
803 				// a tertiary difference may still matter
804 				terSOrder = CollationElementIterator.tertiaryOrder(sOrder);
805 				terTOrder = CollationElementIterator.tertiaryOrder(tOrder);
806 				if (terSOrder != terTOrder) {
807 				    // there is a tertiary difference
808 				    targetCursor.next();
809 				    toffset = targetCursor.getOffset();
810 				    debug("Tertiary difference");
811 				    continue startSearchForFirstMatch;
812 				    // (strength is TERTIARY)
813 				}
814 			    }
815 			}
816 		    } // if (checkSecTer)
817 
818 		}  // if ( pSOrder != pTOrder )
819 	    } // while()
820 
821 	    // There might be a match
822 	    // Just do some final checking
823 	    if (sOrder != CollationElementIterator.NULLORDER) {
824 		// (tOrder must be CollationElementIterator::NULLORDER,
825 		// since this point is only reached when sOrder or
826 		// tOrder is NULLORDER.)
827 		// The source string has more elements,
828 		// but the target string hasn't.
829 		do {
830 		    if (CollationElementIterator.primaryOrder(sOrder) != 0) {
831 			// We found an additional non-ignorable base
832 			// character in the source string.
833 			// This is a primary difference,
834 			// so the source is greater
835 			targetCursor.next();
836 			toffset = targetCursor.getOffset();
837 			debug("Additional non-ignborable base character in source string - source is greater");
838 			continue startSearchForFirstMatch;
839 			// (strength is PRIMARY)
840 		    }
841 		    else if (CollationElementIterator.secondaryOrder(sOrder) != 0) {
842 			// Additional secondary elements mean the
843 			// source string is greater
844 			if (checkSecTer) {
845 			    targetCursor.next();
846 			    toffset = targetCursor.getOffset();
847 			    debug("Additional secondary elements source is greater");
848 			    continue startSearchForFirstMatch;
849 			    // (strength is SECONDARY)
850 			}
851 		    }
852 		} while ((sOrder = sourceCursor.next()) != CollationElementIterator.NULLORDER);
853 	    }
854 	    // Either the target string has more elements,
855 	    // but the source string hasn't or neither the target string
856 	    // or the source string have any more elemnts.
857 	    // In either case the source was contained in target
858 	    // return true;
859 	    return true;
860 	}
861 	return false;
862     }
863 
864 
865 
866     /**
867      * Debug support
868      */
869 
870     private static final boolean debug = false;
debug(Object msg1, Object msg2, Object msg3)871     private static void debug(Object msg1, Object msg2, Object msg3) {
872 	if (debug) {
873 	    System.err.println("HelpUtilities: "+msg1+msg2+msg3);
874 	}
875     }
debug(Object msg1)876     private static void debug(Object msg1) { debug(msg1,"",""); }
debug(Object msg1, Object msg2)877     private static void debug(Object msg1, Object msg2) {
878 	debug(msg1,msg2,"");
879     }
880 
881 }
882