1 /*
2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package build.tools.cldrconverter;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.EnumSet;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35
36 class Bundle {
37 static enum Type {
38 LOCALENAMES, CURRENCYNAMES, TIMEZONENAMES, CALENDARDATA, FORMATDATA;
39
40 static EnumSet<Type> ALL_TYPES = EnumSet.of(LOCALENAMES,
41 CURRENCYNAMES,
42 TIMEZONENAMES,
43 CALENDARDATA,
44 FORMATDATA);
45 }
46
47 private final static Map<String, Bundle> bundles = new HashMap<>();
48
49 private final static String[] NUMBER_PATTERN_KEYS = {
50 "NumberPatterns/decimal",
51 "NumberPatterns/currency",
52 "NumberPatterns/percent"
53 };
54
64 "NumberElements/permille",
65 "NumberElements/infinity",
66 "NumberElements/nan"
67 };
68
69 private final static String[] TIME_PATTERN_KEYS = {
70 "DateTimePatterns/full-time",
71 "DateTimePatterns/long-time",
72 "DateTimePatterns/medium-time",
73 "DateTimePatterns/short-time",
74 };
75
76 private final static String[] DATE_PATTERN_KEYS = {
77 "DateTimePatterns/full-date",
78 "DateTimePatterns/long-date",
79 "DateTimePatterns/medium-date",
80 "DateTimePatterns/short-date",
81 };
82
83 private final static String[] DATETIME_PATTERN_KEYS = {
84 "DateTimePatterns/date-time"
85 };
86
87 private final static String[] ERA_KEYS = {
88 "long.Eras",
89 "Eras",
90 "narrow.Eras"
91 };
92
93 // Keys for individual time zone names
94 private final static String TZ_GEN_LONG_KEY = "timezone.displayname.generic.long";
95 private final static String TZ_GEN_SHORT_KEY = "timezone.displayname.generic.short";
96 private final static String TZ_STD_LONG_KEY = "timezone.displayname.standard.long";
97 private final static String TZ_STD_SHORT_KEY = "timezone.displayname.standard.short";
98 private final static String TZ_DST_LONG_KEY = "timezone.displayname.daylight.long";
99 private final static String TZ_DST_SHORT_KEY = "timezone.displayname.daylight.short";
100 private final static String[] ZONE_NAME_KEYS = {
101 TZ_STD_LONG_KEY,
102 TZ_STD_SHORT_KEY,
103 TZ_DST_LONG_KEY,
104 TZ_DST_SHORT_KEY,
153 return bundleTypes;
154 }
155
156 String getCurrencies() {
157 return currencies;
158 }
159
160 /**
161 * Generate a map that contains all the data that should be
162 * visible for the bundle's locale
163 */
164 Map<String, Object> getTargetMap() throws Exception {
165 String[] cldrBundles = getCLDRPath().split(",");
166
167 // myMap contains resources for id.
168 Map<String, Object> myMap = new HashMap<>();
169 int index;
170 for (index = 0; index < cldrBundles.length; index++) {
171 if (cldrBundles[index].equals(id)) {
172 myMap.putAll(CLDRConverter.getCLDRBundle(cldrBundles[index]));
173 break;
174 }
175 }
176
177 // parentsMap contains resources from id's parents.
178 Map<String, Object> parentsMap = new HashMap<>();
179 for (int i = cldrBundles.length - 1; i > index; i--) {
180 if (!("no".equals(cldrBundles[i]) || cldrBundles[i].startsWith("no_"))) {
181 parentsMap.putAll(CLDRConverter.getCLDRBundle(cldrBundles[i]));
182 }
183 }
184 // Duplicate myMap as parentsMap for "root" so that the
185 // fallback works. This is a huck, though.
186 if ("root".equals(cldrBundles[0])) {
187 assert parentsMap.isEmpty();
188 parentsMap.putAll(myMap);
189 }
190
191 // merge individual strings into arrays
192
193 // if myMap has any of the NumberPatterns members
194 for (String k : NUMBER_PATTERN_KEYS) {
195 if (myMap.containsKey(k)) {
196 String[] numberPatterns = new String[NUMBER_PATTERN_KEYS.length];
197 for (int i = 0; i < NUMBER_PATTERN_KEYS.length; i++) {
198 String key = NUMBER_PATTERN_KEYS[i];
199 String value = (String) myMap.remove(key);
200 if (value == null) {
201 value = (String) parentsMap.remove(key);
202 }
203 if (value.length() == 0) {
204 CLDRConverter.warning("empty pattern for " + key);
205 }
353 for (int i = 0; i < names.length; i++) {
354 if (names[i] == null) {
355 names[i] = enNames[i];
356 }
357 }
358 }
359 // If there are still nulls, give up names.
360 if (hasNulls(names)) {
361 names = null;
362 }
363 }
364 }
365 // replace the Map with the array
366 if (names != null) {
367 myMap.put(key, names);
368 } else {
369 it.remove();
370 }
371 }
372 }
373 return myMap;
374 }
375
376 private void handleMultipleInheritance(Map<String, Object> map, Map<String, Object> parents, String key) {
377 String formatKey = key + "/format";
378 Object format = map.get(formatKey);
379 if (format != null) {
380 map.remove(formatKey);
381 map.put(key, format);
382 if (fillInElements(parents, formatKey, format)) {
383 map.remove(key);
384 }
385 }
386 String standaloneKey = key + "/stand-alone";
387 Object standalone = map.get(standaloneKey);
388 if (standalone != null) {
389 map.remove(standaloneKey);
390 String realKey = key;
391 if (format != null) {
392 realKey = "standalone." + key;
489 int len = patternKeys.length;
490 List<String> rawPatterns = new ArrayList<>(len);
491 List<String> patterns = new ArrayList<>(len);
492 for (int i = 0; i < len; i++) {
493 String key = calendarPrefix + patternKeys[i];
494 String pattern = (String) myMap.remove(key);
495 if (pattern == null) {
496 pattern = (String) parentsMap.remove(key);
497 }
498 rawPatterns.add(i, pattern);
499 if (pattern != null) {
500 patterns.add(i, translateDateFormatLetters(calendarType, pattern));
501 } else {
502 patterns.add(i, null);
503 }
504 }
505 // If patterns is empty or has any nulls, discard patterns.
506 if (patterns.isEmpty()) {
507 return;
508 }
509 for (String p : patterns) {
510 if (p == null) {
511 return;
512 }
513 }
514 String key = calendarPrefix + name;
515 if (!rawPatterns.equals(patterns)) {
516 myMap.put("java.time." + key, rawPatterns.toArray(new String[len]));
517 }
518 myMap.put(key, patterns.toArray(new String[len]));
519 break;
520 }
521 }
522 }
523
524 private String translateDateFormatLetters(CalendarType calendarType, String cldrFormat) {
525 String pattern = cldrFormat;
526 int length = pattern.length();
527 boolean inQuote = false;
528 StringBuilder jrePattern = new StringBuilder(length);
529 int count = 0;
530 char lastLetter = 0;
531
532 for (int i = 0; i < length; i++) {
533 char c = pattern.charAt(i);
613 fillInAbbrs(TZ_STD_LONG_KEY, TZ_STD_SHORT_KEY, map);
614 fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);
615 fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);
616
617 // If the standard std is "Standard Time" and daylight std is "Summer Time",
618 // replace the standard std with the generic std to avoid using
619 // the same abbrivation except for Australia time zone names.
620 String std = map.get(TZ_STD_SHORT_KEY);
621 String dst = map.get(TZ_DST_SHORT_KEY);
622 String gen = map.get(TZ_GEN_SHORT_KEY);
623 if (std != null) {
624 if (dst == null) {
625 // if dst is null, create long and short names from the standard
626 // std. ("Something Standard Time" to "Something Daylight Time",
627 // or "Something Time" to "Something Summer Time")
628 String name = map.get(TZ_STD_LONG_KEY);
629 if (name != null) {
630 if (name.contains("Standard Time")) {
631 name = name.replace("Standard Time", "Daylight Time");
632 } else if (name.endsWith("Mean Time")) {
633 name = name.replace("Mean Time", "Summer Time");
634 } else if (name.endsWith(" Time")) {
635 name = name.replace(" Time", " Summer Time");
636 }
637 map.put(TZ_DST_LONG_KEY, name);
638 fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);
639 }
640 }
641 if (gen == null) {
642 String name = map.get(TZ_STD_LONG_KEY);
643 if (name != null) {
644 if (name.endsWith("Standard Time")) {
645 name = name.replace("Standard Time", "Time");
646 } else if (name.endsWith("Mean Time")) {
647 name = name.replace("Mean Time", "Time");
648 }
649 map.put(TZ_GEN_LONG_KEY, name);
650 fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);
651 }
652 }
653 }
654 }
655
656 private void fillInAbbrs(String longKey, String shortKey, Map<String, String> map) {
657 String abbr = map.get(shortKey);
658 if (abbr == null) {
659 String name = map.get(longKey);
660 if (name != null) {
661 abbr = toAbbr(name);
662 if (abbr != null) {
663 map.put(shortKey, abbr);
664 }
665 }
666 }
667 }
668
695 break;
696
697 // TODO: support 'c' and 'e' in JRE SimpleDateFormat
698 // Use 'u' and 'E' for now.
699 case 'c':
700 case 'e':
701 switch (count) {
702 case 1:
703 sb.append('u');
704 break;
705 case 3:
706 case 4:
707 appendN('E', count, sb);
708 break;
709 case 5:
710 appendN('E', 3, sb);
711 break;
712 }
713 break;
714
715 case 'v':
716 case 'V':
717 appendN('z', count, sb);
718 break;
719
720 case 'Z':
721 if (count == 4 || count == 5) {
722 sb.append("XXX");
723 }
724 break;
725
726 case 'u':
727 case 'U':
728 case 'q':
729 case 'Q':
730 case 'l':
731 case 'g':
732 case 'j':
733 case 'A':
734 throw new InternalError(String.format("Unsupported letter: '%c', count=%d%n",
735 cldrLetter, count));
736 default:
737 appendN(cldrLetter, count, sb);
738 break;
739 }
740 }
741
742 private void appendN(char c, int n, StringBuilder sb) {
743 for (int i = 0; i < n; i++) {
744 sb.append(c);
745 }
746 }
747
748 private static boolean hasNulls(Object[] array) {
749 for (int i = 0; i < array.length; i++) {
750 if (array[i] == null) {
751 return true;
752 }
753 }
754 return false;
755 }
|
1 /*
2 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package build.tools.cldrconverter;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.EnumSet;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Objects;
36
37 class Bundle {
38 static enum Type {
39 LOCALENAMES, CURRENCYNAMES, TIMEZONENAMES, CALENDARDATA, FORMATDATA;
40
41 static EnumSet<Type> ALL_TYPES = EnumSet.of(LOCALENAMES,
42 CURRENCYNAMES,
43 TIMEZONENAMES,
44 CALENDARDATA,
45 FORMATDATA);
46 }
47
48 private final static Map<String, Bundle> bundles = new HashMap<>();
49
50 private final static String[] NUMBER_PATTERN_KEYS = {
51 "NumberPatterns/decimal",
52 "NumberPatterns/currency",
53 "NumberPatterns/percent"
54 };
55
65 "NumberElements/permille",
66 "NumberElements/infinity",
67 "NumberElements/nan"
68 };
69
70 private final static String[] TIME_PATTERN_KEYS = {
71 "DateTimePatterns/full-time",
72 "DateTimePatterns/long-time",
73 "DateTimePatterns/medium-time",
74 "DateTimePatterns/short-time",
75 };
76
77 private final static String[] DATE_PATTERN_KEYS = {
78 "DateTimePatterns/full-date",
79 "DateTimePatterns/long-date",
80 "DateTimePatterns/medium-date",
81 "DateTimePatterns/short-date",
82 };
83
84 private final static String[] DATETIME_PATTERN_KEYS = {
85 "DateTimePatterns/full-dateTime",
86 "DateTimePatterns/long-dateTime",
87 "DateTimePatterns/medium-dateTime",
88 "DateTimePatterns/short-dateTime",
89 };
90
91 private final static String[] ERA_KEYS = {
92 "long.Eras",
93 "Eras",
94 "narrow.Eras"
95 };
96
97 // Keys for individual time zone names
98 private final static String TZ_GEN_LONG_KEY = "timezone.displayname.generic.long";
99 private final static String TZ_GEN_SHORT_KEY = "timezone.displayname.generic.short";
100 private final static String TZ_STD_LONG_KEY = "timezone.displayname.standard.long";
101 private final static String TZ_STD_SHORT_KEY = "timezone.displayname.standard.short";
102 private final static String TZ_DST_LONG_KEY = "timezone.displayname.daylight.long";
103 private final static String TZ_DST_SHORT_KEY = "timezone.displayname.daylight.short";
104 private final static String[] ZONE_NAME_KEYS = {
105 TZ_STD_LONG_KEY,
106 TZ_STD_SHORT_KEY,
107 TZ_DST_LONG_KEY,
108 TZ_DST_SHORT_KEY,
157 return bundleTypes;
158 }
159
160 String getCurrencies() {
161 return currencies;
162 }
163
164 /**
165 * Generate a map that contains all the data that should be
166 * visible for the bundle's locale
167 */
168 Map<String, Object> getTargetMap() throws Exception {
169 String[] cldrBundles = getCLDRPath().split(",");
170
171 // myMap contains resources for id.
172 Map<String, Object> myMap = new HashMap<>();
173 int index;
174 for (index = 0; index < cldrBundles.length; index++) {
175 if (cldrBundles[index].equals(id)) {
176 myMap.putAll(CLDRConverter.getCLDRBundle(cldrBundles[index]));
177 CLDRConverter.handleAliases(myMap);
178 break;
179 }
180 }
181
182 // parentsMap contains resources from id's parents.
183 Map<String, Object> parentsMap = new HashMap<>();
184 for (int i = cldrBundles.length - 1; i > index; i--) {
185 if (!("no".equals(cldrBundles[i]) || cldrBundles[i].startsWith("no_"))) {
186 parentsMap.putAll(CLDRConverter.getCLDRBundle(cldrBundles[i]));
187 CLDRConverter.handleAliases(parentsMap);
188 }
189 }
190 // Duplicate myMap as parentsMap for "root" so that the
191 // fallback works. This is a hack, though.
192 if ("root".equals(cldrBundles[0])) {
193 assert parentsMap.isEmpty();
194 parentsMap.putAll(myMap);
195 }
196
197 // merge individual strings into arrays
198
199 // if myMap has any of the NumberPatterns members
200 for (String k : NUMBER_PATTERN_KEYS) {
201 if (myMap.containsKey(k)) {
202 String[] numberPatterns = new String[NUMBER_PATTERN_KEYS.length];
203 for (int i = 0; i < NUMBER_PATTERN_KEYS.length; i++) {
204 String key = NUMBER_PATTERN_KEYS[i];
205 String value = (String) myMap.remove(key);
206 if (value == null) {
207 value = (String) parentsMap.remove(key);
208 }
209 if (value.length() == 0) {
210 CLDRConverter.warning("empty pattern for " + key);
211 }
359 for (int i = 0; i < names.length; i++) {
360 if (names[i] == null) {
361 names[i] = enNames[i];
362 }
363 }
364 }
365 // If there are still nulls, give up names.
366 if (hasNulls(names)) {
367 names = null;
368 }
369 }
370 }
371 // replace the Map with the array
372 if (names != null) {
373 myMap.put(key, names);
374 } else {
375 it.remove();
376 }
377 }
378 }
379
380 // Remove all duplicates
381 if (Objects.nonNull(parentsMap)) {
382 for (Iterator<String> it = myMap.keySet().iterator(); it.hasNext();) {
383 String key = it.next();
384 if (Objects.deepEquals(parentsMap.get(key), myMap.get(key))) {
385 it.remove();
386 }
387 }
388 }
389
390 return myMap;
391 }
392
393 private void handleMultipleInheritance(Map<String, Object> map, Map<String, Object> parents, String key) {
394 String formatKey = key + "/format";
395 Object format = map.get(formatKey);
396 if (format != null) {
397 map.remove(formatKey);
398 map.put(key, format);
399 if (fillInElements(parents, formatKey, format)) {
400 map.remove(key);
401 }
402 }
403 String standaloneKey = key + "/stand-alone";
404 Object standalone = map.get(standaloneKey);
405 if (standalone != null) {
406 map.remove(standaloneKey);
407 String realKey = key;
408 if (format != null) {
409 realKey = "standalone." + key;
506 int len = patternKeys.length;
507 List<String> rawPatterns = new ArrayList<>(len);
508 List<String> patterns = new ArrayList<>(len);
509 for (int i = 0; i < len; i++) {
510 String key = calendarPrefix + patternKeys[i];
511 String pattern = (String) myMap.remove(key);
512 if (pattern == null) {
513 pattern = (String) parentsMap.remove(key);
514 }
515 rawPatterns.add(i, pattern);
516 if (pattern != null) {
517 patterns.add(i, translateDateFormatLetters(calendarType, pattern));
518 } else {
519 patterns.add(i, null);
520 }
521 }
522 // If patterns is empty or has any nulls, discard patterns.
523 if (patterns.isEmpty()) {
524 return;
525 }
526 String key = calendarPrefix + name;
527 if (!rawPatterns.equals(patterns)) {
528 myMap.put("java.time." + key, rawPatterns.toArray(new String[len]));
529 }
530 myMap.put(key, patterns.toArray(new String[len]));
531 break;
532 }
533 }
534 }
535
536 private String translateDateFormatLetters(CalendarType calendarType, String cldrFormat) {
537 String pattern = cldrFormat;
538 int length = pattern.length();
539 boolean inQuote = false;
540 StringBuilder jrePattern = new StringBuilder(length);
541 int count = 0;
542 char lastLetter = 0;
543
544 for (int i = 0; i < length; i++) {
545 char c = pattern.charAt(i);
625 fillInAbbrs(TZ_STD_LONG_KEY, TZ_STD_SHORT_KEY, map);
626 fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);
627 fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);
628
629 // If the standard std is "Standard Time" and daylight std is "Summer Time",
630 // replace the standard std with the generic std to avoid using
631 // the same abbrivation except for Australia time zone names.
632 String std = map.get(TZ_STD_SHORT_KEY);
633 String dst = map.get(TZ_DST_SHORT_KEY);
634 String gen = map.get(TZ_GEN_SHORT_KEY);
635 if (std != null) {
636 if (dst == null) {
637 // if dst is null, create long and short names from the standard
638 // std. ("Something Standard Time" to "Something Daylight Time",
639 // or "Something Time" to "Something Summer Time")
640 String name = map.get(TZ_STD_LONG_KEY);
641 if (name != null) {
642 if (name.contains("Standard Time")) {
643 name = name.replace("Standard Time", "Daylight Time");
644 } else if (name.endsWith("Mean Time")) {
645 if (!name.startsWith("Greenwich ")) {
646 name = name.replace("Mean Time", "Summer Time");
647 }
648 } else if (name.endsWith(" Time")) {
649 name = name.replace(" Time", " Summer Time");
650 }
651 map.put(TZ_DST_LONG_KEY, name);
652 fillInAbbrs(TZ_DST_LONG_KEY, TZ_DST_SHORT_KEY, map);
653 }
654 }
655 if (gen == null) {
656 String name = map.get(TZ_STD_LONG_KEY);
657 if (name != null) {
658 if (name.endsWith("Standard Time")) {
659 name = name.replace("Standard Time", "Time");
660 } else if (name.endsWith("Mean Time")) {
661 if (!name.startsWith("Greenwich ")) {
662 name = name.replace("Mean Time", "Time");
663 }
664 }
665 map.put(TZ_GEN_LONG_KEY, name);
666 fillInAbbrs(TZ_GEN_LONG_KEY, TZ_GEN_SHORT_KEY, map);
667 }
668 }
669 }
670 }
671
672 private void fillInAbbrs(String longKey, String shortKey, Map<String, String> map) {
673 String abbr = map.get(shortKey);
674 if (abbr == null) {
675 String name = map.get(longKey);
676 if (name != null) {
677 abbr = toAbbr(name);
678 if (abbr != null) {
679 map.put(shortKey, abbr);
680 }
681 }
682 }
683 }
684
711 break;
712
713 // TODO: support 'c' and 'e' in JRE SimpleDateFormat
714 // Use 'u' and 'E' for now.
715 case 'c':
716 case 'e':
717 switch (count) {
718 case 1:
719 sb.append('u');
720 break;
721 case 3:
722 case 4:
723 appendN('E', count, sb);
724 break;
725 case 5:
726 appendN('E', 3, sb);
727 break;
728 }
729 break;
730
731 case 'l':
732 // 'l' is deprecated as a pattern character. Should be ignored.
733 break;
734
735 case 'u':
736 // Use 'y' for now.
737 appendN('y', count, sb);
738 break;
739
740 case 'v':
741 case 'V':
742 appendN('z', count, sb);
743 break;
744
745 case 'Z':
746 if (count == 4 || count == 5) {
747 sb.append("XXX");
748 }
749 break;
750
751 case 'U':
752 case 'q':
753 case 'Q':
754 case 'g':
755 case 'j':
756 case 'A':
757 throw new InternalError(String.format("Unsupported letter: '%c', count=%d, id=%s%n",
758 cldrLetter, count, id));
759 default:
760 appendN(cldrLetter, count, sb);
761 break;
762 }
763 }
764
765 private void appendN(char c, int n, StringBuilder sb) {
766 for (int i = 0; i < n; i++) {
767 sb.append(c);
768 }
769 }
770
771 private static boolean hasNulls(Object[] array) {
772 for (int i = 0; i < array.length; i++) {
773 if (array[i] == null) {
774 return true;
775 }
776 }
777 return false;
778 }
|