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