KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > saxon > functions > FormatNumber2


1 package net.sf.saxon.functions;
2 import net.sf.saxon.Controller;
3 import net.sf.saxon.tinytree.CharSlice;
4 import net.sf.saxon.expr.Expression;
5 import net.sf.saxon.expr.StaticContext;
6 import net.sf.saxon.expr.Token;
7 import net.sf.saxon.expr.XPathContext;
8 import net.sf.saxon.om.*;
9 import net.sf.saxon.style.ExpressionContext;
10 import net.sf.saxon.trans.*;
11 import net.sf.saxon.value.*;
12
13 import java.io.Serializable JavaDoc;
14 import java.math.BigDecimal JavaDoc;
15 import java.math.BigInteger JavaDoc;
16 import java.util.ArrayList JavaDoc;
17 import java.util.List JavaDoc;
18
19 /**
20 * XSLT 2.0 implementation of format-number() function - removes the dependence on the JDK.
21 */

22
23 public class FormatNumber2 extends SystemFunction implements XSLTFunction {
24
25     private NamespaceResolver nsContext = null;
26         // held only if the third argument is present, and its value is not known statically
27

28     private DecimalSymbols decimalFormatSymbols = null;
29         // held only if the decimal format to use can be determined statically
30

31     private transient String JavaDoc picture = null;
32         // held transiently at compile time if the picture is known statically
33

34     private SubPicture[] subPictures = null;
35         // held if the picture is known statically
36

37     private boolean requireFixup = false;
38         // used to detect when an unknown decimal-format name is used
39

40     private transient boolean checked = false;
41         // the second time checkArguments is called, it's a global check so the static context is inaccurate
42

43
44     public void checkArguments(StaticContext env) throws XPathException {
45         if (checked) return;
46         checked = true;
47         super.checkArguments(env);
48         if (argument[1] instanceof StringValue) {
49             // picture is known statically - optimize for this common case
50
picture = ((StringValue)argument[1]).getStringValue();
51         }
52         if (argument.length==3) {
53             if (argument[2] instanceof StringValue) {
54                 // common case, decimal format name is supplied as a string literal
55

56                 String JavaDoc qname = ((StringValue)argument[2]).getStringValue();
57                 String JavaDoc dfLocalName;
58                 String JavaDoc dfURI;
59                 try {
60                     String JavaDoc[] parts = Name.getQNameParts(qname);
61                     dfLocalName = parts[1];
62                     dfURI = env.getURIForPrefix(parts[0]);
63                 } catch (QNameException e) {
64                     throw new StaticError("Invalid decimal format name. " + e.getMessage());
65                 }
66
67                 DecimalFormatManager dfm = ((ExpressionContext)env).getXSLStylesheet().getDecimalFormatManager();
68                 requireFixup = true;
69                 dfm.registerUsage(dfURI, dfLocalName, this);
70                     // this causes a callback to the fixup() method, either now, or later if it's a forwards reference
71
} else {
72                 // we need to save the namespace context
73
nsContext = env.getNamespaceResolver();
74             }
75         } else {
76             // two arguments only: it uses the default decimal format
77
if (env instanceof ExpressionContext) {
78                 // this is XSLT
79
DecimalFormatManager dfm = ((ExpressionContext)env).getXSLStylesheet().getDecimalFormatManager();
80                 dfm.registerUsage("", "", this);
81                 // Note: if using the "default default", there will be no fixup call.
82
} else {
83                 // using saxon:decimal-format in some other environment
84
}
85         }
86     }
87
88     /**
89     * Fixup: this is a callback from the DecimalFormatManager used once the xsl:decimal-format
90     * element is identified
91     */

92
93     public void fixup(DecimalSymbols dfs) {
94         // System.err.println("Fixed up format-number, picture=" + picture);
95
requireFixup = false;
96         decimalFormatSymbols = dfs;
97         if (picture != null) {
98             try {
99                 subPictures = getSubPictures(picture, dfs);
100             } catch (XPathException err) {
101                 subPictures = null;
102                     // we'll report the error at run-time
103
}
104         }
105     }
106
107     /**
108     * Analyze a picture string into two sub-pictures.
109     * @return an array of two sub-pictures, the positive and the negative sub-pictures respectively.
110     * If there is only one sub-picture, the second one is null.
111     */

112
113     private SubPicture[] getSubPictures(String JavaDoc picture, DecimalSymbols dfs) throws XPathException {
114         int[] picture4 = StringValue.expand(picture);
115         SubPicture[] pics = new SubPicture[2];
116         if (picture4.length==0) {
117             DynamicError err = new DynamicError("format-number() picture is zero-length");
118             err.setErrorCode("XTDE1310");
119             throw err;
120         }
121         int sep = -1;
122         for (int c=0; c<picture4.length; c++) {
123             if (picture4[c] == dfs.patternSeparator) {
124                 if (c==0) {
125                     grumble("first subpicture is zero-length");
126                 } else if (sep >= 0) {
127                     grumble("more than one pattern separator");
128                 } else if (sep == picture4.length-1) {
129                     grumble("second subpicture is zero-length");
130                 }
131                 sep = c;
132             }
133         }
134
135         if (sep<0) {
136             pics[0] = new SubPicture(picture4, dfs);
137             pics[1] = null;
138         } else {
139             int[] pic0 = new int[sep];
140             System.arraycopy(picture4, 0, pic0, 0, sep);
141             int[] pic1 = new int[picture4.length - sep - 1];
142             System.arraycopy(picture4, sep+1, pic1, 0, picture4.length - sep - 1);
143             pics[0] = new SubPicture(pic0, dfs);
144             pics[1] = new SubPicture(pic1, dfs);
145         }
146         return pics;
147     }
148
149     /**
150     * preEvaluate: this method suppresses compile-time evaluation by doing nothing.
151     * We can't evaluate early because we don't have access to the DecimalFormatManager.
152     */

153
154     public Expression preEvaluate(StaticContext env) throws XPathException {
155         return this;
156     }
157
158     /**
159     * Evaluate in a context where a string is wanted
160     */

161
162     public String JavaDoc evaluateAsString(XPathContext context) throws XPathException {
163
164         int numArgs = argument.length;
165         Controller ctrl = context.getController();
166
167         DecimalSymbols dfs = decimalFormatSymbols;
168
169         AtomicValue av0 = (AtomicValue)argument[0].evaluateItem(context);
170         if (av0 == null) {
171             av0 = DoubleValue.NaN;
172         };
173         NumericValue number = (NumericValue)av0.getPrimitiveValue();
174
175         if (dfs == null) {
176             // the decimal-format name was not resolved statically
177
if (requireFixup) {
178                 // we registered for a fixup, but none came
179
dynamicError("Unknown decimal format name", "XTDE1280", context);
180                 return null;
181             }
182             DecimalFormatManager dfm = ctrl.getExecutable().getDecimalFormatManager();
183             if (numArgs==2) {
184                 dfs = dfm.getDefaultDecimalFormat();
185             } else {
186                 // the decimal-format name was given as a run-time expression
187
String JavaDoc qname = argument[2].evaluateItem(context).getStringValue();
188                 try {
189                     String JavaDoc[] parts = Name.getQNameParts(qname);
190                     String JavaDoc localName = parts[1];
191                     String JavaDoc uri = nsContext.getURIForPrefix(parts[0], false);
192                     if (uri==null) {
193                         dynamicError("Namespace prefix '" + parts[0] + "' has not been defined", "XTDE1280", context);
194                         return null;
195                     }
196                     dfs = dfm.getNamedDecimalFormat(uri, localName);
197                     if (dfs==null) {
198                         dynamicError(
199                             "format-number function: decimal-format '" + localName + "' is not defined", "XTDE1280", context);
200                         return null;
201                     }
202                 } catch (QNameException e) {
203                     dynamicError("Invalid decimal format name. " + e.getMessage(), "XTDE1280", context);
204                 }
205             }
206         }
207         SubPicture[] pics = subPictures;
208         if (pics == null) {
209             String JavaDoc format = argument[1].evaluateItem(context).getStringValue();
210             pics = getSubPictures(format, dfs);
211         }
212         return formatNumber(number, pics, dfs).toString();
213     }
214
215     /**
216     * Evaluate in a general context
217     */

218
219     public Item evaluateItem(XPathContext c) throws XPathException {
220         return new StringValue(evaluateAsString(c));
221     }
222
223     /**
224     * Format a number, given the two subpictures and the decimal format symbols
225     */

226
227     private CharSequence JavaDoc formatNumber(NumericValue number,
228                                       SubPicture[] subPictures,
229                                       DecimalSymbols dfs) {
230
231         NumericValue absN = number;
232         SubPicture pic;
233         String JavaDoc minusSign = "";
234         if (number.signum() < 0) {
235             absN = number.negate();
236             if (subPictures[1]==null) {
237                 pic = subPictures[0];
238                 minusSign = "" + unicodeChar(dfs.minusSign);
239             } else {
240                 pic = subPictures[1];
241             }
242         } else {
243             pic = subPictures[0];
244         }
245
246         return pic.format(absN, dfs, minusSign);
247     }
248
249     private void grumble(String JavaDoc s) throws XPathException {
250         dynamicError("format-number picture: " + s, "XTDE1310", null);
251     }
252
253     /**
254     * Inner class to represent one sub-picture (the negative or positive subpicture)
255     */

256
257     private class SubPicture implements Serializable JavaDoc {
258
259         int minWholePartSize = 0;
260         int maxWholePartSize = 0;
261         int minFractionPartSize = 0;
262         int maxFractionPartSize = 0;
263         boolean isPercent = false;
264         boolean isPerMille = false;
265         String JavaDoc prefix = "";
266         String JavaDoc suffix = "";
267         int[] wholePartGroupingPositions = null;
268         int[] fractionalPartGroupingPositions = null;
269
270         public SubPicture(int[] pic, DecimalSymbols dfs) throws XPathException {
271
272             final int percentSign = dfs.percent;
273             final int perMilleSign = dfs.permill;
274             final int decimalSeparator = dfs.decimalSeparator;
275             final int groupingSeparator = dfs.groupingSeparator;
276             final int digitSign = dfs.digit;
277             final int zeroDigit = dfs.zeroDigit;
278
279             List JavaDoc wholePartPositions = null;
280             List JavaDoc fractionalPartPositions = null;
281
282             boolean foundDigit = false;
283             for (int i=0; i<pic.length; i++) {
284                 if (pic[i] == digitSign || pic[i] == zeroDigit) {
285                     foundDigit = true;
286                     break;
287                 }
288             }
289             if (!foundDigit) {
290                 grumble("subpicture contains no digit or zero-digit sign");
291             }
292
293             int phase = 0;
294                 // phase = 0: passive characters at start
295
// phase = 1: digit signs in whole part
296
// phase = 2: zero-digit signs in whole part
297
// phase = 3: zero-digit signs in fractional part
298
// phase = 4: digit signs in fractional part
299
// phase = 5: passive characters at end
300

301             for (int i=0; i<pic.length; i++) {
302                 int c = pic[i];
303
304                 if (c == percentSign || c == perMilleSign) {
305                     if (isPercent || isPerMille) {
306                         grumble("Cannot have more than one percent or per-mille character in a sub-picture");
307                     }
308                     isPercent = (c==percentSign);
309                     isPerMille = (c==perMilleSign);
310                     switch (phase) {
311                         case 0:
312                             prefix += unicodeChar(c);
313                             break;
314                         case 1:
315                         case 2:
316                         case 3:
317                         case 4:
318                         case 5:
319                             phase = 5;
320                             suffix += unicodeChar(c);
321                             break;
322                     }
323                 } else if (c == digitSign) {
324                     switch (phase) {
325                         case 0:
326                         case 1:
327                             phase = 1;
328                             maxWholePartSize++;
329                             break;
330                         case 2:
331                             grumble("Digit sign must not appear after a zero-digit sign in the integer part of a sub-picture");
332                             break;
333                         case 3:
334                         case 4:
335                             phase = 4;
336                             maxFractionPartSize++;
337                             break;
338                         case 5:
339                             grumble("Passive character must not appear between active characters in a sub-picture");
340                             break;
341                     }
342                 } else if (c == zeroDigit) {
343                     switch (phase) {
344                         case 0:
345                         case 1:
346                         case 2:
347                             phase = 2;
348                             minWholePartSize++;
349                             maxWholePartSize++;
350                             break;
351                         case 3:
352                             minFractionPartSize++;
353                             maxFractionPartSize++;
354                             break;
355                         case 4:
356                             grumble("Zero digit sign must not appear after a digit sign in the fractional part of a sub-picture");
357                             break;
358                         case 5:
359                             grumble("Passive character must not appear between active characters in a sub-picture");
360                             break;
361                     }
362                 } else if (c == decimalSeparator) {
363                     switch (phase) {
364                         case 0:
365                         case 1:
366                         case 2:
367                             phase = 3;
368                             break;
369                         case 3:
370                         case 4:
371                         case 5:
372                             grumble("There must only be one decimal separator in a sub-picture");
373                             break;
374                     }
375                 } else if (c == groupingSeparator) {
376                     switch (phase) {
377                         case 0:
378                         case 1:
379                         case 2:
380                             if (wholePartPositions == null) {
381                                 wholePartPositions = new ArrayList JavaDoc(3);
382                             }
383                             wholePartPositions.add(new Integer JavaDoc(maxWholePartSize));
384                                 // note these are positions from a false offset, they will be corrected later
385
break;
386                         case 3:
387                         case 4:
388                             if (maxFractionPartSize == 0) {
389                                 grumble("Grouping separator cannot be adjacent to decimal separator");
390                             }
391                             if (fractionalPartPositions == null) {
392                                 fractionalPartPositions = new ArrayList JavaDoc(3);
393                             }
394                             fractionalPartPositions.add(new Integer JavaDoc(maxFractionPartSize));
395                             break;
396                         case 5:
397                             grumble("Grouping separator found in suffix of sub-picture");
398                             break;
399                     }
400                 } else { // passive character found
401
switch (phase) {
402                         case 0:
403                             prefix += unicodeChar(c);
404                             break;
405                         case 1:
406                         case 2:
407                         case 3:
408                         case 4:
409                         case 5:
410                             phase = 5;
411                             suffix += unicodeChar(c);
412                             break;
413                     }
414                 }
415             }
416
417             // System.err.println("minWholePartSize = " + minWholePartSize);
418
// System.err.println("maxWholePartSize = " + maxWholePartSize);
419
// System.err.println("minFractionPartSize = " + minFractionPartSize);
420
// System.err.println("maxFractionPartSize = " + maxFractionPartSize);
421

422             // Sort out the grouping positions
423

424             if (wholePartPositions != null) {
425                 // convert to positions relative to the decimal separator
426
int n = wholePartPositions.size();
427                 wholePartGroupingPositions = new int[n];
428                 for (int i=0; i<n; i++) {
429                     wholePartGroupingPositions[i] =
430                         maxWholePartSize - ((Integer JavaDoc)wholePartPositions.get(n - i - 1)).intValue();
431                 }
432                 if (n > 1) {
433                     boolean regular = true;
434                     int first = wholePartGroupingPositions[0];
435                     for (int i=1; i<n; i++) {
436                         if (wholePartGroupingPositions[i] != i * first) {
437                             regular = false;
438                             break;
439                         }
440                     }
441                     if (regular) {
442                         wholePartGroupingPositions = new int[1];
443                         wholePartGroupingPositions[0] = first;
444                     }
445                 }
446                 if (wholePartGroupingPositions[0] == 0) {
447                     grumble("Cannot have a grouping separator adjacent to the decimal separator");
448                 }
449             }
450
451             if (fractionalPartPositions != null) {
452                 int n = fractionalPartPositions.size();
453                 fractionalPartGroupingPositions = new int[n];
454                 for (int i=0; i<n; i++) {
455                     fractionalPartGroupingPositions[i] =
456                         ((Integer JavaDoc)fractionalPartPositions.get(i)).intValue();
457                 }
458             }
459         }
460
461         /**
462         * Format a number using this sub-picture
463         * @param value the absolute value of the number to be formatted
464         */

465
466         public CharSequence JavaDoc format(NumericValue value, DecimalSymbols dfs, String JavaDoc minusSign) {
467
468             // System.err.println("Formatting " + value);
469

470             if (value.isNaN()) {
471                 return prefix + dfs.NaN + suffix;
472             }
473
474             if (value instanceof DoubleValue && Double.isInfinite(value.getDoubleValue())) {
475                 return minusSign + prefix + dfs.infinity + suffix;
476             }
477
478             if (value instanceof FloatValue && Double.isInfinite(value.getDoubleValue())) {
479                 return minusSign + prefix + dfs.infinity + suffix;
480             }
481
482             int multiplier = 1;
483             if (isPercent) {
484                 multiplier = 100;
485             } else if (isPerMille) {
486                 multiplier = 1000;
487             }
488
489             if (multiplier != 1) {
490                 try {
491                     value = value.arithmetic(Token.MULT, new IntegerValue(multiplier), null);
492                 } catch (XPathException e) {
493                     value = new DoubleValue(value.getDoubleValue() * multiplier);
494                 }
495             }
496
497             StringBuffer JavaDoc sb = new StringBuffer JavaDoc(20);
498             if (value instanceof DoubleValue || value instanceof FloatValue) {
499                 formatDouble(value.getDoubleValue(), sb);
500
501             } else if (value instanceof IntegerValue || value instanceof BigIntegerValue) {
502                 formatInteger(value, sb);
503
504             } else if (value instanceof DecimalValue) {
505                 formatDecimal((DecimalValue)value, sb);
506             }
507
508             // System.err.println("Justified number: " + sb.toString());
509

510             // Map the digits and decimal point to use the selected characters
511

512             int[] ib = StringValue.expand(sb);
513             int ibused = ib.length;
514             int point = sb.indexOf(".");
515             if (point == -1) {
516                 point = sb.length();
517             } else {
518                 ib[point] = dfs.decimalSeparator;
519
520                 // If there is no fractional part, delete the decimal point
521
if (maxFractionPartSize == 0) {
522                     ibused--;
523                 }
524             }
525
526             // Map the digits
527

528             if (dfs.zeroDigit != '0') {
529                 int newZero = dfs.zeroDigit;
530                 for (int i=0; i<ibused; i++) {
531                     int c = ib[i];
532                     if (c>='0' && c<='9') {
533                         ib[i] = (c-'0'+newZero);
534                     }
535                 }
536             }
537
538             // Add the whole-part grouping separators
539

540             if (wholePartGroupingPositions != null) {
541                 if (wholePartGroupingPositions.length == 1) {
542                     // grouping separators are at regular positions
543
int g = wholePartGroupingPositions[0];
544                     int p = point - g;
545                     while (p > 0) {
546                         ib = insert(ib, ibused++, dfs.groupingSeparator, p);
547                         //sb.insert(p, unicodeChar(dfs.groupingSeparator));
548
p -= g;
549                     }
550                 } else {
551                     // grouping separators are at irregular positions
552
for (int i=0; i<wholePartGroupingPositions.length; i++) {
553                         int p = point - wholePartGroupingPositions[i];
554                         if (p > 0) {
555                             ib = insert(ib, ibused++, dfs.groupingSeparator, p);
556                             //sb.insert(p, unicodeChar(dfs.groupingSeparator));
557
}
558                     }
559                 }
560             }
561
562             // Add the fractional-part grouping separators
563

564             if (fractionalPartGroupingPositions != null) {
565                     // grouping separators are at irregular positions.
566
for (int i=0; i<fractionalPartGroupingPositions.length; i++) {
567                     int p = point + 1 + fractionalPartGroupingPositions[i] + i;
568                     if (p < ibused-1) {
569                         ib = insert(ib, ibused++, dfs.groupingSeparator, p);
570                         //sb.insert(p, dfs.groupingSeparator);
571
} else {
572                         break;
573                     }
574                 }
575             }
576
577             // System.err.println("Grouped number: " + sb.toString());
578

579             //sb.insert(0, prefix);
580
//sb.insert(0, minusSign);
581
//sb.append(suffix);
582
FastStringBuffer res = new FastStringBuffer(prefix.length() + minusSign.length() + suffix.length() + ibused);
583             res.append(minusSign);
584             res.append(prefix);
585             res.append(StringValue.contract(ib, ibused));
586             res.append(suffix);
587             return res;
588         }
589
590         private void formatDecimal(DecimalValue value, StringBuffer JavaDoc sb) {
591             BigDecimal JavaDoc dval = value.getValue();
592             dval = dval.setScale(maxFractionPartSize, BigDecimal.ROUND_HALF_EVEN);
593             sb.append(dval.toString());
594
595             int point = sb.indexOf(".");
596             int intDigits;
597             if (point >= 0) {
598                 int zz = maxFractionPartSize - minFractionPartSize;
599                 while (zz>0) {
600                     if (sb.charAt(sb.length()-1) == '0') {
601                         sb.setLength(sb.length()-1);
602                         zz--;
603                     } else {
604                         break;
605                     }
606                 }
607                 intDigits = point;
608                 if (sb.charAt(sb.length()-1) == '.') {
609                     sb.setLength(sb.length()-1);
610                 }
611             } else {
612                 intDigits = sb.length();
613             }
614             for (int i=0; i<(minWholePartSize - intDigits); i++) {
615                 sb.insert(0, '0');
616             }
617         }
618
619         private void formatInteger(NumericValue value, StringBuffer JavaDoc sb) {
620             sb.append(value.toString());
621             int leadingZeroes = minWholePartSize - sb.length();
622             for (int i=0; i < leadingZeroes; i++) {
623                 sb.insert(0, '0');
624             }
625             sb.append('.');
626             for (int i=0; i < minFractionPartSize; i++) {
627                 sb.append('0');
628             }
629         }
630
631         /**
632          * Format a number supplied as a double
633          * @param value the double value
634          * @param sb StringBuffer to contain the formatted value
635          */

636         private void formatDouble(double value, StringBuffer JavaDoc sb) {
637             // Convert to a scaled integer, by multiplying by 10^d where d is the maximum fraction size
638

639             double d = value;
640             if (maxFractionPartSize != 0) {
641                 d *= Math.pow(10, maxFractionPartSize);
642             }
643             int point;
644             if (Math.abs(d) > Long.MAX_VALUE) {
645                 // If this exceeds the size of a long, construct a BigInteger
646
long bits = Double.doubleToLongBits(value);
647                 boolean negative = (bits & 0x8000000000000000L) != 0;
648                 int exponent = (int)((bits & 0x7ff0000000000000L)>>52) - 1023 - 52;
649                 long mantissa = bits & 0x000fffffffffffffL | 0x0010000000000000L;
650                 BigInteger JavaDoc big = BigInteger.valueOf(mantissa);
651                 big = big.multiply(BigInteger.valueOf(2).pow(exponent));
652
653                 if (negative) {
654                     sb.append('-');
655                 }
656                 sb.append(big.toString());
657                 // add a decimal point, it will be removed later if not needed
658
sb.append('.');
659
660                 // there is no fractional part
661

662                 // TODO: Java DecimalFormat gives nicer results for large
663
// values, e.g. 1e19 comes out as 10,000,000,000,000,000,000
664
// whereas we are producing 10,000,018,432,000,000,000.
665

666             } else {
667                 long ld = (long)d;
668
669                 // Now apply any rounding needed, using the "round half to even" rule
670

671                 double rem = d - ld;
672                 if (rem > 0.5) {
673                     ld++;
674                 } else if (rem == 0.5) {
675                     // round half to even - check the last bit
676
if ((ld & 1) == 1) {
677                         ld++;
678                     }
679                 }
680
681                 String JavaDoc sd = "" + ld; // use Java integer-to-string conversion
682
int wholeSize = sd.length() - maxFractionPartSize;
683                 if (wholeSize > 0) {
684                     sb.append(sd.substring(0, wholeSize));
685                 }
686
687                 point = sb.length();
688                 sb.append('.');
689
690                 while (wholeSize < 0) {
691                     sb.append('0');
692                     wholeSize++;
693                 }
694                 sb.append(sd.substring(wholeSize));
695
696                 // System.err.println("Rounded number: " + sb.toString());
697

698                 while (point < minWholePartSize) {
699                     sb.insert(0, '0');
700                     point++;
701                 }
702
703                 while (point > maxWholePartSize) {
704                     if (sb.charAt(0)=='0') {
705                         sb.deleteCharAt(0);
706                         point--;
707                     } else {
708                         break;
709                     }
710                 }
711
712                 int actualFractionSize = sb.length()-point-1;
713                 while (actualFractionSize > minFractionPartSize) {
714                     if (sb.charAt(sb.length()-1) == '0') {
715                         sb.deleteCharAt(sb.length()-1);
716                         actualFractionSize--;
717                     } else {
718                         break;
719                     }
720                 }
721
722                 if (sb.charAt(sb.length()-1) == '.') {
723                      sb.deleteCharAt(sb.length()-1);
724                 }
725             }
726         }
727     }
728
729     /**
730      * Convert a Unicode character (possibly >65536) to a String, using a surrogate pair if necessary
731      * @param ch the Unicode codepoint value
732      * @return a string representing the Unicode codepoint, either a string of one character or a surrogate pair
733      */

734
735     private static CharSequence JavaDoc unicodeChar(int ch) {
736         if (ch<65536) {
737             return "" + (char)ch;
738         }
739         else { // output a surrogate pair
740
//To compute the numeric value of the character corresponding to a surrogate
741
//pair, use this formula (all numbers are hex):
742
//(FirstChar - D800) * 400 + (SecondChar - DC00) + 10000
743
ch -= 65536;
744             char[] sb = new char[2];
745             sb[0] = ((char)((ch / 1024) + 55296));
746             sb[1] = ((char)((ch % 1024) + 56320));
747             return new CharSlice(sb, 0, 2);
748         }
749     }
750
751     /**
752      * Insert an integer into an array of integers
753      */

754
755     private static int[] insert(int[] array, int used, int value, int position) {
756         if (used+1 > array.length) {
757             int[] a2 = new int[used+10];
758             System.arraycopy(array, 0, a2, 0, used);
759             array = a2;
760         }
761         for (int i=used-1; i>=position; i--) {
762             array[i+1] = array[i];
763         }
764         array[position] = value;
765         return array;
766     }
767 }
768
769 //
770
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
771
// you may not use this file except in compliance with the License. You may obtain a copy of the
772
// License at http://www.mozilla.org/MPL/
773
//
774
// Software distributed under the License is distributed on an "AS IS" basis,
775
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
776
// See the License for the specific language governing rights and limitations under the License.
777
//
778
// The Original Code is: all this file.
779
//
780
// The Initial Developer of the Original Code is Michael H. Kay
781
//
782
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
783
//
784
// Contributor(s): none.
785
//
786
Popular Tags