1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 /*
   5  * Licensed to the Apache Software Foundation (ASF) under one or more
   6  * contributor license agreements.  See the NOTICE file distributed with
   7  * this work for additional information regarding copyright ownership.
   8  * The ASF licenses this file to You under the Apache License, Version 2.0
   9  * (the "License"); you may not use this file except in compliance with
  10  * the License.  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xml.internal.utils;
  22 
  23 import java.text.CollationElementIterator;
  24 import java.text.Collator;
  25 import java.text.RuleBasedCollator;
  26 import java.util.Locale;
  27 
  28 
  29 /**
  30 * International friendly string comparison with case-order
  31  * @author Igor Hersht, igorh@ca.ibm.com
  32  * @LastModified: Oct 2017
  33 */
  34 public class StringComparable implements Comparable<StringComparable>  {
  35 
  36      public final static int UNKNOWN_CASE = -1;
  37      public final static int UPPER_CASE = 1;
  38      public final static int LOWER_CASE = 2;
  39 
  40      private  String m_text;
  41      private  Locale m_locale;
  42      private RuleBasedCollator m_collator;
  43      private String m_caseOrder;
  44      private int m_mask = 0xFFFFFFFF;
  45 
  46     public StringComparable(final String text, final Locale locale,
  47             final Collator collator, final String caseOrder){
  48          m_text =  text;
  49          m_locale = locale;
  50          m_collator = (RuleBasedCollator)collator;
  51          m_caseOrder = caseOrder;
  52          m_mask = getMask(m_collator.getStrength());
  53     }
  54 
  55     @SuppressWarnings({"rawtypes", "unchecked"})
  56     public final static Comparable getComparator( final String text, final Locale locale,
  57             final Collator collator, final String caseOrder){
  58         if((caseOrder == null) ||(caseOrder.length() == 0)){// no case-order specified
  59             return  ((RuleBasedCollator)collator).getCollationKey(text);
  60         }else{
  61             return new StringComparable(text, locale, collator, caseOrder);
  62         }
  63     }
  64 
  65    public final String toString(){return m_text;}
  66 
  67      public int compareTo(StringComparable o) {
  68      final String pattern = o.toString();
  69      if(m_text.equals(pattern)){//Code-point equals
  70         return 0;
  71      }
  72      final int savedStrength = m_collator.getStrength();
  73      int comp = 0;
  74       // Is there difference more significant than case-order?
  75      if(((savedStrength == Collator.PRIMARY) || (savedStrength == Collator.SECONDARY))){
  76          comp = m_collator.compare(m_text, pattern );
  77      }else{// more than SECONDARY
  78          m_collator.setStrength(Collator.SECONDARY);
  79          comp = m_collator.compare(m_text, pattern );
  80          m_collator.setStrength(savedStrength);
  81      }
  82      if(comp != 0){//Difference more significant than case-order
  83         return comp ;
  84      }
  85 
  86       // No difference more significant than case-order.
  87       // Find case difference
  88        comp = getCaseDiff(m_text, pattern);
  89        if(comp != 0){
  90            return comp;
  91        }else{// No case differences. Less significant difference could exist
  92             return m_collator.compare(m_text, pattern );
  93        }
  94   }
  95 
  96 
  97   private final int getCaseDiff (final String text, final String pattern){
  98      final int savedStrength = m_collator.getStrength();
  99      final int savedDecomposition = m_collator.getDecomposition();
 100      m_collator.setStrength(Collator.TERTIARY);// not to ignore case
 101      m_collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION );// corresponds NDF
 102 
 103     final int diff[] =getFirstCaseDiff (text, pattern, m_locale);
 104     m_collator.setStrength(savedStrength);// restore
 105     m_collator.setDecomposition(savedDecomposition); //restore
 106     if(diff != null){
 107        if((m_caseOrder).equals("upper-first")){
 108             if(diff[0] == UPPER_CASE){
 109                 return -1;
 110             }else{
 111                 return 1;
 112             }
 113        }else{// lower-first
 114             if(diff[0] == LOWER_CASE){
 115                 return -1;
 116             }else{
 117                 return 1;
 118             }
 119        }
 120    }else{// No case differences
 121         return 0;
 122    }
 123 
 124   }
 125 
 126 
 127 
 128   private final int[] getFirstCaseDiff(final String text, final String pattern, final Locale locale){
 129 
 130         final CollationElementIterator targIter = m_collator.getCollationElementIterator(text);
 131         final CollationElementIterator patIter = m_collator.getCollationElementIterator(pattern);
 132         int startTarg = -1;
 133         int endTarg = -1;
 134         int startPatt = -1;
 135         int endPatt = -1;
 136         final int done = getElement(CollationElementIterator.NULLORDER);
 137         int patternElement = 0, targetElement = 0;
 138         boolean getPattern = true, getTarget = true;
 139 
 140         while (true) {
 141             if (getPattern){
 142                  startPatt = patIter.getOffset();
 143                  patternElement = getElement(patIter.next());
 144                  endPatt = patIter.getOffset();
 145             }
 146             if ((getTarget)){
 147                  startTarg  = targIter.getOffset();
 148                  targetElement   = getElement(targIter.next());
 149                  endTarg  = targIter.getOffset();
 150             }
 151             getTarget = getPattern = true;
 152             if ((patternElement == done) ||( targetElement == done)) {
 153                 return null;
 154             } else if (targetElement == 0) {
 155               getPattern = false;
 156             } else if (patternElement == 0) {
 157               getTarget = false;
 158             } else if (targetElement != patternElement) {// mismatch
 159                 if((startPatt < endPatt) && (startTarg < endTarg)){
 160                     final String  subText = text.substring(startTarg, endTarg);
 161                     final String  subPatt = pattern.substring(startPatt, endPatt);
 162                     final String  subTextUp = subText.toUpperCase(locale);
 163                     final String  subPattUp = subPatt.toUpperCase(locale);
 164                     if(m_collator.compare(subTextUp, subPattUp) != 0){ // not case diffference
 165                         continue;
 166                     }
 167 
 168                     int diff[] = {UNKNOWN_CASE, UNKNOWN_CASE};
 169                     if(m_collator.compare(subText, subTextUp) == 0){
 170                         diff[0] = UPPER_CASE;
 171                     }else if(m_collator.compare(subText, subText.toLowerCase(locale)) == 0){
 172                        diff[0] = LOWER_CASE;
 173                     }
 174                     if(m_collator.compare(subPatt, subPattUp) == 0){
 175                         diff[1] = UPPER_CASE;
 176                     }else if(m_collator.compare(subPatt, subPatt.toLowerCase(locale)) == 0){
 177                        diff[1] = LOWER_CASE;
 178                     }
 179 
 180                     if(((diff[0] == UPPER_CASE) && ( diff[1] == LOWER_CASE)) ||
 181                        ((diff[0] == LOWER_CASE) && ( diff[1] == UPPER_CASE))){
 182                         return diff;
 183                     }else{// not case diff
 184                       continue;
 185                     }
 186                 }else{
 187                     continue;
 188                 }
 189 
 190            }
 191         }
 192 
 193   }
 194 
 195 
 196  // Return a mask for the part of the order we're interested in
 197     private static final int getMask(final int strength) {
 198         switch (strength) {
 199             case Collator.PRIMARY:
 200                 return 0xFFFF0000;
 201             case Collator.SECONDARY:
 202                 return 0xFFFFFF00;
 203             default:
 204                 return 0xFFFFFFFF;
 205         }
 206     }
 207     //get collation element with given strength
 208     // from the element with max strength
 209   private final int getElement(int maxStrengthElement){
 210 
 211     return (maxStrengthElement & m_mask);
 212   }
 213 
 214 }//StringComparable