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) 2009-2016, International Business Machines
6 *   Corporation and others.  All Rights Reserved.
7 *******************************************************************************
8 */
9
10package com.ibm.icu.impl;
11
12import java.io.IOException;
13import java.nio.ByteBuffer;
14
15import com.ibm.icu.text.Normalizer;
16import com.ibm.icu.text.Normalizer2;
17import com.ibm.icu.util.ICUUncheckedIOException;
18
19public final class Norm2AllModes {
20    // Public API dispatch via Normalizer2 subclasses -------------------------- ***
21
22    // Normalizer2 implementation for the old UNORM_NONE.
23    public static final class NoopNormalizer2 extends Normalizer2 {
24        @Override
25        public StringBuilder normalize(CharSequence src, StringBuilder dest) {
26            if(dest!=src) {
27                dest.setLength(0);
28                return dest.append(src);
29            } else {
30                throw new IllegalArgumentException();
31            }
32        }
33        @Override
34        public Appendable normalize(CharSequence src, Appendable dest) {
35            if(dest!=src) {
36                try {
37                    return dest.append(src);
38                } catch(IOException e) {
39                    throw new ICUUncheckedIOException(e);  // Avoid declaring "throws IOException".
40                }
41            } else {
42                throw new IllegalArgumentException();
43            }
44        }
45        @Override
46        public StringBuilder normalizeSecondAndAppend(StringBuilder first, CharSequence second) {
47            if(first!=second) {
48                return first.append(second);
49            } else {
50                throw new IllegalArgumentException();
51            }
52        }
53        @Override
54        public StringBuilder append(StringBuilder first, CharSequence second) {
55            if(first!=second) {
56                return first.append(second);
57            } else {
58                throw new IllegalArgumentException();
59            }
60        }
61        @Override
62        public String getDecomposition(int c) {
63            return null;
64        }
65        // No need to override the default getRawDecomposition().
66        @Override
67        public boolean isNormalized(CharSequence s) { return true; }
68        @Override
69        public Normalizer.QuickCheckResult quickCheck(CharSequence s) { return Normalizer.YES; }
70        @Override
71        public int spanQuickCheckYes(CharSequence s) { return s.length(); }
72        @Override
73        public boolean hasBoundaryBefore(int c) { return true; }
74        @Override
75        public boolean hasBoundaryAfter(int c) { return true; }
76        @Override
77        public boolean isInert(int c) { return true; }
78    }
79
80    // Intermediate class:
81    // Has Normalizer2Impl and does boilerplate argument checking and setup.
82    public static abstract class Normalizer2WithImpl extends Normalizer2 {
83        public Normalizer2WithImpl(Normalizer2Impl ni) {
84            impl=ni;
85        }
86
87        // normalize
88        @Override
89        public StringBuilder normalize(CharSequence src, StringBuilder dest) {
90            if(dest==src) {
91                throw new IllegalArgumentException();
92            }
93            dest.setLength(0);
94            normalize(src, new Normalizer2Impl.ReorderingBuffer(impl, dest, src.length()));
95            return dest;
96        }
97        @Override
98        public Appendable normalize(CharSequence src, Appendable dest) {
99            if(dest==src) {
100                throw new IllegalArgumentException();
101            }
102            Normalizer2Impl.ReorderingBuffer buffer=
103                new Normalizer2Impl.ReorderingBuffer(impl, dest, src.length());
104            normalize(src, buffer);
105            buffer.flush();
106            return dest;
107        }
108        protected abstract void normalize(CharSequence src, Normalizer2Impl.ReorderingBuffer buffer);
109
110        // normalize and append
111        @Override
112        public StringBuilder normalizeSecondAndAppend(StringBuilder first, CharSequence second) {
113            return normalizeSecondAndAppend(first, second, true);
114        }
115        @Override
116        public StringBuilder append(StringBuilder first, CharSequence second) {
117            return normalizeSecondAndAppend(first, second, false);
118        }
119        public StringBuilder normalizeSecondAndAppend(
120                StringBuilder first, CharSequence second, boolean doNormalize) {
121            if(first==second) {
122                throw new IllegalArgumentException();
123            }
124            normalizeAndAppend(
125                second, doNormalize,
126                new Normalizer2Impl.ReorderingBuffer(impl, first, first.length()+second.length()));
127            return first;
128        }
129        protected abstract void normalizeAndAppend(
130                CharSequence src, boolean doNormalize, Normalizer2Impl.ReorderingBuffer buffer);
131
132        @Override
133        public String getDecomposition(int c) {
134            return impl.getDecomposition(c);
135        }
136        @Override
137        public String getRawDecomposition(int c) {
138            return impl.getRawDecomposition(c);
139        }
140        @Override
141        public int composePair(int a, int b) {
142            return impl.composePair(a, b);
143        }
144
145        @Override
146        public int getCombiningClass(int c) {
147            return impl.getCC(impl.getNorm16(c));
148        }
149
150        // quick checks
151        @Override
152        public boolean isNormalized(CharSequence s) {
153            return s.length()==spanQuickCheckYes(s);
154        }
155        @Override
156        public Normalizer.QuickCheckResult quickCheck(CharSequence s) {
157            return isNormalized(s) ? Normalizer.YES : Normalizer.NO;
158        }
159
160        public abstract int getQuickCheck(int c);
161
162        public final Normalizer2Impl impl;
163    }
164
165    public static final class DecomposeNormalizer2 extends Normalizer2WithImpl {
166        public DecomposeNormalizer2(Normalizer2Impl ni) {
167            super(ni);
168        }
169
170        @Override
171        protected void normalize(CharSequence src, Normalizer2Impl.ReorderingBuffer buffer) {
172            impl.decompose(src, 0, src.length(), buffer);
173        }
174        @Override
175        protected void normalizeAndAppend(
176                CharSequence src, boolean doNormalize, Normalizer2Impl.ReorderingBuffer buffer) {
177            impl.decomposeAndAppend(src, doNormalize, buffer);
178        }
179        @Override
180        public int spanQuickCheckYes(CharSequence s) {
181            return impl.decompose(s, 0, s.length(), null);
182        }
183        @Override
184        public int getQuickCheck(int c) {
185            return impl.isDecompYes(impl.getNorm16(c)) ? 1 : 0;
186        }
187        @Override
188        public boolean hasBoundaryBefore(int c) { return impl.hasDecompBoundary(c, true); }
189        @Override
190        public boolean hasBoundaryAfter(int c) { return impl.hasDecompBoundary(c, false); }
191        @Override
192        public boolean isInert(int c) { return impl.isDecompInert(c); }
193    }
194
195    public static final class ComposeNormalizer2 extends Normalizer2WithImpl {
196        public ComposeNormalizer2(Normalizer2Impl ni, boolean fcc) {
197            super(ni);
198            onlyContiguous=fcc;
199        }
200
201        @Override
202        protected void normalize(CharSequence src, Normalizer2Impl.ReorderingBuffer buffer) {
203            impl.compose(src, 0, src.length(), onlyContiguous, true, buffer);
204        }
205        @Override
206        protected void normalizeAndAppend(
207                CharSequence src, boolean doNormalize, Normalizer2Impl.ReorderingBuffer buffer) {
208            impl.composeAndAppend(src, doNormalize, onlyContiguous, buffer);
209        }
210
211        @Override
212        public boolean isNormalized(CharSequence s) {
213            // 5: small destCapacity for substring normalization
214            return impl.compose(s, 0, s.length(),
215                                onlyContiguous, false,
216                                new Normalizer2Impl.ReorderingBuffer(impl, new StringBuilder(), 5));
217        }
218        @Override
219        public Normalizer.QuickCheckResult quickCheck(CharSequence s) {
220            int spanLengthAndMaybe=impl.composeQuickCheck(s, 0, s.length(), onlyContiguous, false);
221            if((spanLengthAndMaybe&1)!=0) {
222                return Normalizer.MAYBE;
223            } else if((spanLengthAndMaybe>>>1)==s.length()) {
224                return Normalizer.YES;
225            } else {
226                return Normalizer.NO;
227            }
228        }
229        @Override
230        public int spanQuickCheckYes(CharSequence s) {
231            return impl.composeQuickCheck(s, 0, s.length(), onlyContiguous, true)>>>1;
232        }
233        @Override
234        public int getQuickCheck(int c) {
235            return impl.getCompQuickCheck(impl.getNorm16(c));
236        }
237        @Override
238        public boolean hasBoundaryBefore(int c) { return impl.hasCompBoundaryBefore(c); }
239        @Override
240        public boolean hasBoundaryAfter(int c) {
241            return impl.hasCompBoundaryAfter(c, onlyContiguous, false);
242        }
243        @Override
244        public boolean isInert(int c) {
245            return impl.hasCompBoundaryAfter(c, onlyContiguous, true);
246        }
247
248        private final boolean onlyContiguous;
249    }
250
251    public static final class FCDNormalizer2 extends Normalizer2WithImpl {
252        public FCDNormalizer2(Normalizer2Impl ni) {
253            super(ni);
254        }
255
256        @Override
257        protected void normalize(CharSequence src, Normalizer2Impl.ReorderingBuffer buffer) {
258            impl.makeFCD(src, 0, src.length(), buffer);
259        }
260        @Override
261        protected void normalizeAndAppend(
262                CharSequence src, boolean doNormalize, Normalizer2Impl.ReorderingBuffer buffer) {
263            impl.makeFCDAndAppend(src, doNormalize, buffer);
264        }
265        @Override
266        public int spanQuickCheckYes(CharSequence s) {
267            return impl.makeFCD(s, 0, s.length(), null);
268        }
269        @Override
270        public int getQuickCheck(int c) {
271            return impl.isDecompYes(impl.getNorm16(c)) ? 1 : 0;
272        }
273        @Override
274        public boolean hasBoundaryBefore(int c) { return impl.hasFCDBoundaryBefore(c); }
275        @Override
276        public boolean hasBoundaryAfter(int c) { return impl.hasFCDBoundaryAfter(c); }
277        @Override
278        public boolean isInert(int c) { return impl.isFCDInert(c); }
279    }
280
281    // instance cache ---------------------------------------------------------- ***
282
283    private Norm2AllModes(Normalizer2Impl ni) {
284        impl=ni;
285        comp=new ComposeNormalizer2(ni, false);
286        decomp=new DecomposeNormalizer2(ni);
287        fcd=new FCDNormalizer2(ni);
288        fcc=new ComposeNormalizer2(ni, true);
289    }
290
291    public final Normalizer2Impl impl;
292    public final ComposeNormalizer2 comp;
293    public final DecomposeNormalizer2 decomp;
294    public final FCDNormalizer2 fcd;
295    public final ComposeNormalizer2 fcc;
296
297    private static Norm2AllModes getInstanceFromSingleton(Norm2AllModesSingleton singleton) {
298        if(singleton.exception!=null) {
299            throw singleton.exception;
300        }
301        return singleton.allModes;
302    }
303    public static Norm2AllModes getNFCInstance() {
304        return getInstanceFromSingleton(NFCSingleton.INSTANCE);
305    }
306    public static Norm2AllModes getNFKCInstance() {
307        return getInstanceFromSingleton(NFKCSingleton.INSTANCE);
308    }
309    public static Norm2AllModes getNFKC_CFInstance() {
310        return getInstanceFromSingleton(NFKC_CFSingleton.INSTANCE);
311    }
312    // For use in properties APIs.
313    public static Normalizer2WithImpl getN2WithImpl(int index) {
314        switch(index) {
315        case 0: return getNFCInstance().decomp;  // NFD
316        case 1: return getNFKCInstance().decomp; // NFKD
317        case 2: return getNFCInstance().comp;    // NFC
318        case 3: return getNFKCInstance().comp;   // NFKC
319        default: return null;
320        }
321    }
322    public static Norm2AllModes getInstance(ByteBuffer bytes, String name) {
323        if(bytes==null) {
324            Norm2AllModesSingleton singleton;
325            if(name.equals("nfc")) {
326                singleton=NFCSingleton.INSTANCE;
327            } else if(name.equals("nfkc")) {
328                singleton=NFKCSingleton.INSTANCE;
329            } else if(name.equals("nfkc_cf")) {
330                singleton=NFKC_CFSingleton.INSTANCE;
331            } else {
332                singleton=null;
333            }
334            if(singleton!=null) {
335                if(singleton.exception!=null) {
336                    throw singleton.exception;
337                }
338                return singleton.allModes;
339            }
340        }
341        return cache.getInstance(name, bytes);
342    }
343    private static CacheBase<String, Norm2AllModes, ByteBuffer> cache =
344        new SoftCache<String, Norm2AllModes, ByteBuffer>() {
345            @Override
346            protected Norm2AllModes createInstance(String key, ByteBuffer bytes) {
347                Normalizer2Impl impl;
348                if(bytes==null) {
349                    impl=new Normalizer2Impl().load(key+".nrm");
350                } else {
351                    impl=new Normalizer2Impl().load(bytes);
352                }
353                return new Norm2AllModes(impl);
354            }
355        };
356
357    public static final NoopNormalizer2 NOOP_NORMALIZER2=new NoopNormalizer2();
358    /**
359     * Gets the FCD normalizer, with the FCD data initialized.
360     * @return FCD normalizer
361     */
362    public static Normalizer2 getFCDNormalizer2() {
363        return getNFCInstance().fcd;
364    }
365
366    private static final class Norm2AllModesSingleton {
367        private Norm2AllModesSingleton(String name) {
368            try {
369                Normalizer2Impl impl=new Normalizer2Impl().load(name+".nrm");
370                allModes=new Norm2AllModes(impl);
371            } catch(RuntimeException e) {
372                exception=e;
373            }
374        }
375
376        private Norm2AllModes allModes;
377        private RuntimeException exception;
378    }
379    private static final class NFCSingleton {
380        private static final Norm2AllModesSingleton INSTANCE=new Norm2AllModesSingleton("nfc");
381    }
382    private static final class NFKCSingleton {
383        private static final Norm2AllModesSingleton INSTANCE=new Norm2AllModesSingleton("nfkc");
384    }
385    private static final class NFKC_CFSingleton {
386        private static final Norm2AllModesSingleton INSTANCE=new Norm2AllModesSingleton("nfkc_cf");
387    }
388}
389