1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/*
4 *******************************************************************************
5 * Copyright (C) 2003-2010, International Business Machines Corporation and    *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8*/
9package com.ibm.icu.dev.test.stringprep;
10
11
12import java.lang.reflect.InvocationTargetException;
13import java.lang.reflect.Method;
14
15import com.ibm.icu.impl.ICUResourceBundle;
16import com.ibm.icu.lang.UCharacter;
17import com.ibm.icu.lang.UCharacterDirection;
18import com.ibm.icu.text.StringPrepParseException;
19import com.ibm.icu.text.UCharacterIterator;
20import com.ibm.icu.text.UnicodeSet;
21
22/**
23 * @author ram
24 *
25 * To change the template for this generated type comment go to
26 * Window>Preferences>Java>Code Generation>Code and Comments
27 */
28public class NamePrepTransform {
29
30    private static final NamePrepTransform transform = new NamePrepTransform();
31
32    private UnicodeSet labelSeparatorSet;
33    private UnicodeSet prohibitedSet;
34    private UnicodeSet unassignedSet;
35    private MapTransform mapTransform;
36    public static final int NONE = 0;
37    public static final int ALLOW_UNASSIGNED = 1;
38
39    private NamePrepTransform(){
40        // load the resource bundle
41        ICUResourceBundle bundle = (ICUResourceBundle)ICUResourceBundle.getBundleInstance("com/ibm/icu/dev/data/testdata","idna_rules", NamePrepTransform.class.getClassLoader(), true);
42        String  mapRules      = bundle.getString("MapNoNormalization");
43        mapRules             += bundle.getString("MapNFKC");
44        // disable
45        mapTransform          = new MapTransform("CaseMap", mapRules, 0 /*Transliterator.FORWARD*/);
46        labelSeparatorSet     = new UnicodeSet(bundle.getString("LabelSeparatorSet"));
47        prohibitedSet         = new UnicodeSet(bundle.getString("ProhibitedSet"));
48        unassignedSet         = new UnicodeSet(bundle.getString("UnassignedSet"));
49    }
50
51    public static final NamePrepTransform getInstance(){
52        return transform;
53    }
54    public static boolean isLabelSeparator(int ch){
55        return transform.labelSeparatorSet.contains(ch);
56    }
57
58     /*
59       1) Map -- For each character in the input, check if it has a mapping
60          and, if so, replace it with its mapping.
61
62       2) Normalize -- Possibly normalize the result of step 1 using Unicode
63          normalization.
64
65       3) Prohibit -- Check for any characters that are not allowed in the
66          output.  If any are found, return an error.
67
68       4) Check bidi -- Possibly check for right-to-left characters, and if
69          any are found, make sure that the whole string satisfies the
70          requirements for bidirectional strings.  If the string does not
71          satisfy the requirements for bidirectional strings, return an
72          error.
73          [Unicode3.2] defines several bidirectional categories; each character
74           has one bidirectional category assigned to it.  For the purposes of
75           the requirements below, an "RandALCat character" is a character that
76           has Unicode bidirectional categories "R" or "AL"; an "LCat character"
77           is a character that has Unicode bidirectional category "L".  Note
78
79
80           that there are many characters which fall in neither of the above
81           definitions; Latin digits (<U+0030> through <U+0039>) are examples of
82           this because they have bidirectional category "EN".
83
84           In any profile that specifies bidirectional character handling, all
85           three of the following requirements MUST be met:
86
87           1) The characters in section 5.8 MUST be prohibited.
88
89           2) If a string contains any RandALCat character, the string MUST NOT
90              contain any LCat character.
91
92           3) If a string contains any RandALCat character, a RandALCat
93              character MUST be the first character of the string, and a
94              RandALCat character MUST be the last character of the string.
95    */
96
97    public boolean isReady() {
98        return mapTransform.isReady();
99    }
100
101    public StringBuffer prepare(UCharacterIterator src,
102                                       int options)
103                                       throws StringPrepParseException{
104             return prepare(src.getText(),options);
105    }
106
107    private String map ( String src, int options)
108                                throws StringPrepParseException{
109        // map
110        boolean allowUnassigned =  ((options & ALLOW_UNASSIGNED)>0);
111        // disable test
112        String caseMapOut = mapTransform.transliterate(src);
113        UCharacterIterator iter = UCharacterIterator.getInstance(caseMapOut);
114        int ch;
115        while((ch=iter.nextCodePoint())!=UCharacterIterator.DONE){
116            if(transform.unassignedSet.contains(ch)==true && allowUnassigned ==false){
117                throw new StringPrepParseException("An unassigned code point was found in the input",
118                                         StringPrepParseException.UNASSIGNED_ERROR);
119            }
120        }
121        return caseMapOut;
122    }
123    public StringBuffer prepare(String src,int options)
124                                   throws StringPrepParseException{
125
126        int ch;
127        String mapOut = map(src,options);
128        UCharacterIterator iter = UCharacterIterator.getInstance(mapOut);
129
130        int direction=UCharacterDirection.CHAR_DIRECTION_COUNT,
131            firstCharDir=UCharacterDirection.CHAR_DIRECTION_COUNT;
132        int rtlPos=-1, ltrPos=-1;
133        boolean rightToLeft=false, leftToRight=false;
134
135        while((ch=iter.nextCodePoint())!= UCharacterIterator.DONE){
136
137
138            if(transform.prohibitedSet.contains(ch)==true && ch!=0x0020){
139                throw new StringPrepParseException("A prohibited code point was found in the input",
140                                         StringPrepParseException.PROHIBITED_ERROR,
141                                         iter.getText(),iter.getIndex());
142            }
143
144            direction = UCharacter.getDirection(ch);
145            if(firstCharDir == UCharacterDirection.CHAR_DIRECTION_COUNT){
146                firstCharDir = direction;
147            }
148            if(direction == UCharacterDirection.LEFT_TO_RIGHT){
149                leftToRight = true;
150                ltrPos = iter.getIndex()-1;
151            }
152            if(direction == UCharacterDirection.RIGHT_TO_LEFT || direction == UCharacterDirection.RIGHT_TO_LEFT_ARABIC){
153                rightToLeft = true;
154                rtlPos = iter.getIndex()-1;
155            }
156        }
157
158        // satisfy 2
159        if( leftToRight == true && rightToLeft == true){
160            throw new StringPrepParseException("The input does not conform to the rules for BiDi code points.",
161                                     StringPrepParseException.CHECK_BIDI_ERROR,iter.getText(),(rtlPos>ltrPos) ? rtlPos : ltrPos);
162        }
163
164        //satisfy 3
165        if( rightToLeft == true &&
166            !((firstCharDir == UCharacterDirection.RIGHT_TO_LEFT || firstCharDir == UCharacterDirection.RIGHT_TO_LEFT_ARABIC) &&
167            (direction == UCharacterDirection.RIGHT_TO_LEFT || direction == UCharacterDirection.RIGHT_TO_LEFT_ARABIC))
168           ){
169            throw new StringPrepParseException("The input does not conform to the rules for BiDi code points.",
170                                      StringPrepParseException.CHECK_BIDI_ERROR,iter.getText(),(rtlPos>ltrPos) ? rtlPos : ltrPos);
171        }
172
173        return new StringBuffer(mapOut);
174
175    }
176
177    private static class MapTransform {
178        private Object translitInstance;
179        private Method translitMethod;
180        private boolean isReady;
181
182        MapTransform(String id, String rule, int direction) {
183            isReady = initialize(id, rule, direction);
184        }
185
186        boolean initialize(String id, String rule, int direction) {
187            try {
188                Class cls = Class.forName("com.ibm.icu.text.Transliterator");
189                Method createMethod = cls.getMethod("createFromRules", String.class, String.class, Integer.TYPE);
190                translitInstance = createMethod.invoke(null, id, rule, Integer.valueOf(direction));
191                translitMethod = cls.getMethod("transliterate", String.class);
192            } catch (Throwable e) {
193                return false;
194            }
195            return true;
196        }
197
198        boolean isReady() {
199            return isReady;
200        }
201
202        String transliterate(String text) {
203            if (!isReady) {
204                throw new IllegalStateException("Transliterator is not ready");
205            }
206            String result = null;
207            try {
208                result = (String)translitMethod.invoke(translitInstance, text);
209            } catch (InvocationTargetException ite) {
210                throw new RuntimeException(ite);
211            } catch (IllegalAccessException iae) {
212                throw new RuntimeException(iae);
213            }
214            return result;
215        }
216    }
217}
218