1 /* Writing Java ResourceBundles.
2 Copyright (C) 2001-2003, 2005-2010, 2014, 2016, 2018-2020 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 #include <alloca.h>
22
23 /* Specification. */
24 #include "write-java.h"
25
26 #include <errno.h>
27 #include <limits.h>
28 #include <stdbool.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32
33 #include <sys/stat.h>
34 #if !defined S_ISDIR && defined S_IFDIR
35 # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
36 #endif
37 #if !S_IRUSR && S_IREAD
38 # define S_IRUSR S_IREAD
39 #endif
40 #if !S_IRUSR
41 # define S_IRUSR 00400
42 #endif
43 #if !S_IWUSR && S_IWRITE
44 # define S_IWUSR S_IWRITE
45 #endif
46 #if !S_IWUSR
47 # define S_IWUSR 00200
48 #endif
49 #if !S_IXUSR && S_IEXEC
50 # define S_IXUSR S_IEXEC
51 #endif
52 #if !S_IXUSR
53 # define S_IXUSR 00100
54 #endif
55
56 #include "c-ctype.h"
57 #include "error.h"
58 #include "xerror.h"
59 #include "xvasprintf.h"
60 #include "javacomp.h"
61 #include "message.h"
62 #include "msgfmt.h"
63 #include "msgl-iconv.h"
64 #include "msgl-header.h"
65 #include "plural-exp.h"
66 #include "po-charset.h"
67 #include "xalloc.h"
68 #include "xmalloca.h"
69 #include "minmax.h"
70 #include "concat-filename.h"
71 #include "fwriteerror.h"
72 #include "clean-temp.h"
73 #include "unistr.h"
74 #include "gettext.h"
75
76 #define _(str) gettext (str)
77
78
79 /* Check that the resource name is a valid Java class name. To simplify
80 things, we allow only ASCII characters in the class name.
81 Return the number of dots in the class name, or -1 if not OK. */
82 static int
check_resource_name(const char * name)83 check_resource_name (const char *name)
84 {
85 int ndots = 0;
86 const char *p = name;
87
88 for (;;)
89 {
90 /* First character, see Character.isJavaIdentifierStart. */
91 if (!(c_isalpha (*p) || (*p == '$') || (*p == '_')))
92 return -1;
93 /* Following characters, see Character.isJavaIdentifierPart. */
94 do
95 p++;
96 while (c_isalpha (*p) || (*p == '$') || (*p == '_') || c_isdigit (*p));
97 if (*p == '\0')
98 break;
99 if (*p != '.')
100 return -1;
101 p++;
102 ndots++;
103 }
104 return ndots;
105 }
106
107
108 /* Return the Java hash code of a string mod 2^31.
109 The Java String.hashCode() function returns the same values across
110 Java implementations.
111 (See http://www.javasoft.com/docs/books/jls/clarify.html)
112 It returns a signed 32-bit integer. We add a mod 2^31 afterwards;
113 this removes one bit but greatly simplifies the following "mod hash_size"
114 and "mod (hash_size - 2)" operations. */
115 static unsigned int
string_hashcode(const char * str)116 string_hashcode (const char *str)
117 {
118 const char *str_limit = str + strlen (str);
119 int hash = 0;
120 while (str < str_limit)
121 {
122 ucs4_t uc;
123 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
124 if (uc < 0x10000)
125 /* Single UCS-2 'char'. */
126 hash = 31 * hash + uc;
127 else
128 {
129 /* UTF-16 surrogate: two 'char's. */
130 ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10);
131 ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff);
132 hash = 31 * hash + uc1;
133 hash = 31 * hash + uc2;
134 }
135 }
136 return hash & 0x7fffffff;
137 }
138
139
140 /* Return the Java hash code of a (msgctxt, msgid) pair mod 2^31. */
141 static unsigned int
msgid_hashcode(const char * msgctxt,const char * msgid)142 msgid_hashcode (const char *msgctxt, const char *msgid)
143 {
144 if (msgctxt == NULL)
145 return string_hashcode (msgid);
146 else
147 {
148 size_t msgctxt_len = strlen (msgctxt);
149 size_t msgid_len = strlen (msgid);
150 size_t combined_len = msgctxt_len + 1 + msgid_len;
151 char *combined;
152 unsigned int result;
153
154 combined = (char *) xmalloca (combined_len + 1);
155 memcpy (combined, msgctxt, msgctxt_len);
156 combined[msgctxt_len] = MSGCTXT_SEPARATOR;
157 memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1);
158
159 result = string_hashcode (combined);
160
161 freea (combined);
162
163 return result;
164 }
165 }
166
167
168 /* Compute a good hash table size for the given set of msgids. */
169 static unsigned int
compute_hashsize(message_list_ty * mlp,bool * collisionp)170 compute_hashsize (message_list_ty *mlp, bool *collisionp)
171 {
172 /* This is an O(n^2) algorithm, but should be sufficient because few
173 programs have more than 1000 messages in a single domain. */
174 #define XXN 3 /* can be tweaked */
175 #define XXS 3 /* can be tweaked */
176 unsigned int n = mlp->nitems;
177 unsigned int *hashcodes =
178 (unsigned int *) xmalloca (n * sizeof (unsigned int));
179 unsigned int hashsize;
180 unsigned int best_hashsize;
181 unsigned int best_score;
182 size_t j;
183
184 for (j = 0; j < n; j++)
185 hashcodes[j] = msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid);
186
187 /* Try all numbers between n and 3*n. The score depends on the size of the
188 table -- the smaller the better -- and the number of collision lookups,
189 i.e. total number of times that 1 + (hashcode % (hashsize - 2))
190 is added to the index during lookup. If there are collisions, only odd
191 hashsize values are allowed. */
192 best_hashsize = 0;
193 best_score = UINT_MAX;
194 for (hashsize = n; hashsize <= XXN * n; hashsize++)
195 {
196 char *bitmap;
197 unsigned int score;
198
199 /* Premature end of the loop if all future scores are known to be
200 larger than the already reached best_score. This relies on the
201 ascending loop and on the fact that score >= hashsize. */
202 if (hashsize >= best_score)
203 break;
204
205 bitmap = XNMALLOC (hashsize, char);
206 memset (bitmap, 0, hashsize);
207
208 score = 0;
209 for (j = 0; j < n; j++)
210 {
211 unsigned int idx = hashcodes[j] % hashsize;
212
213 if (bitmap[idx] != 0)
214 {
215 /* Collision. Cannot deal with it if hashsize is even. */
216 if ((hashsize % 2) == 0)
217 /* Try next hashsize. */
218 goto bad_hashsize;
219 else
220 {
221 unsigned int idx0 = idx;
222 unsigned int incr = 1 + (hashcodes[j] % (hashsize - 2));
223 score += 2; /* Big penalty for the additional division */
224 do
225 {
226 score++; /* Small penalty for each loop round */
227 idx += incr;
228 if (idx >= hashsize)
229 idx -= hashsize;
230 if (idx == idx0)
231 /* Searching for a hole, we performed a whole round
232 across the table. This happens particularly
233 frequently if gcd(hashsize,incr) > 1. Try next
234 hashsize. */
235 goto bad_hashsize;
236 }
237 while (bitmap[idx] != 0);
238 }
239 }
240 bitmap[idx] = 1;
241 }
242
243 /* Big hashsize also gives a penalty. */
244 score = XXS * score + hashsize;
245
246 /* If for any incr between 1 and hashsize - 2, an whole round
247 (idx0, idx0 + incr, ...) is occupied, and the lookup function
248 must deal with collisions, then some inputs would lead to
249 an endless loop in the lookup function. */
250 if (score > hashsize)
251 {
252 unsigned int incr;
253
254 /* Since the set { idx0, idx0 + incr, ... } depends only on idx0
255 and gcd(hashsize,incr), we only need to conside incr that
256 divides hashsize. */
257 for (incr = 1; incr <= hashsize / 2; incr++)
258 if ((hashsize % incr) == 0)
259 {
260 unsigned int idx0;
261
262 for (idx0 = 0; idx0 < incr; idx0++)
263 {
264 bool full = true;
265 unsigned int idx;
266
267 for (idx = idx0; idx < hashsize; idx += incr)
268 if (bitmap[idx] == 0)
269 {
270 full = false;
271 break;
272 }
273 if (full)
274 /* A whole round is occupied. */
275 goto bad_hashsize;
276 }
277 }
278 }
279
280 if (false)
281 bad_hashsize:
282 score = UINT_MAX;
283
284 free (bitmap);
285
286 if (score < best_score)
287 {
288 best_score = score;
289 best_hashsize = hashsize;
290 }
291 }
292 if (best_hashsize == 0 || best_score < best_hashsize)
293 abort ();
294
295 freea (hashcodes);
296
297 /* There are collisions if and only if best_score > best_hashsize. */
298 *collisionp = (best_score > best_hashsize);
299 return best_hashsize;
300 }
301
302
303 struct table_item { unsigned int index; message_ty *mp; };
304
305 static int
compare_index(const void * pval1,const void * pval2)306 compare_index (const void *pval1, const void *pval2)
307 {
308 return (int)((const struct table_item *) pval1)->index
309 - (int)((const struct table_item *) pval2)->index;
310 }
311
312 /* Compute the list of messages and table indices, sorted according to the
313 indices. */
314 static struct table_item *
compute_table_items(message_list_ty * mlp,unsigned int hashsize)315 compute_table_items (message_list_ty *mlp, unsigned int hashsize)
316 {
317 unsigned int n = mlp->nitems;
318 struct table_item *arr = XNMALLOC (n, struct table_item);
319 char *bitmap;
320 size_t j;
321
322 bitmap = XNMALLOC (hashsize, char);
323 memset (bitmap, 0, hashsize);
324
325 for (j = 0; j < n; j++)
326 {
327 unsigned int hashcode =
328 msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid);
329 unsigned int idx = hashcode % hashsize;
330
331 if (bitmap[idx] != 0)
332 {
333 unsigned int incr = 1 + (hashcode % (hashsize - 2));
334 do
335 {
336 idx += incr;
337 if (idx >= hashsize)
338 idx -= hashsize;
339 }
340 while (bitmap[idx] != 0);
341 }
342 bitmap[idx] = 1;
343
344 arr[j].index = idx;
345 arr[j].mp = mlp->item[j];
346 }
347
348 free (bitmap);
349
350 qsort (arr, n, sizeof (arr[0]), compare_index);
351
352 return arr;
353 }
354
355
356 /* Write a string in Java Unicode notation to the given stream. */
357 static void
write_java_string(FILE * stream,const char * str)358 write_java_string (FILE *stream, const char *str)
359 {
360 static const char hexdigit[] = "0123456789abcdef";
361 const char *str_limit = str + strlen (str);
362
363 fprintf (stream, "\"");
364 while (str < str_limit)
365 {
366 ucs4_t uc;
367 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
368 if (uc < 0x10000)
369 {
370 /* Single UCS-2 'char'. */
371 if (uc == 0x000a)
372 fprintf (stream, "\\n");
373 else if (uc == 0x000d)
374 fprintf (stream, "\\r");
375 else if (uc == 0x0022)
376 fprintf (stream, "\\\"");
377 else if (uc == 0x005c)
378 fprintf (stream, "\\\\");
379 else if (uc >= 0x0020 && uc < 0x007f)
380 fprintf (stream, "%c", (int) uc);
381 else
382 fprintf (stream, "\\u%c%c%c%c",
383 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
384 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
385 }
386 else
387 {
388 /* UTF-16 surrogate: two 'char's. */
389 ucs4_t uc1 = 0xd800 + ((uc - 0x10000) >> 10);
390 ucs4_t uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff);
391 fprintf (stream, "\\u%c%c%c%c",
392 hexdigit[(uc1 >> 12) & 0x0f], hexdigit[(uc1 >> 8) & 0x0f],
393 hexdigit[(uc1 >> 4) & 0x0f], hexdigit[uc1 & 0x0f]);
394 fprintf (stream, "\\u%c%c%c%c",
395 hexdigit[(uc2 >> 12) & 0x0f], hexdigit[(uc2 >> 8) & 0x0f],
396 hexdigit[(uc2 >> 4) & 0x0f], hexdigit[uc2 & 0x0f]);
397 }
398 }
399 fprintf (stream, "\"");
400 }
401
402
403 /* Write a (msgctxt, msgid) pair as a string in Java Unicode notation to the
404 given stream. */
405 static void
write_java_msgid(FILE * stream,message_ty * mp)406 write_java_msgid (FILE *stream, message_ty *mp)
407 {
408 const char *msgctxt = mp->msgctxt;
409 const char *msgid = mp->msgid;
410
411 if (msgctxt == NULL)
412 write_java_string (stream, msgid);
413 else
414 {
415 size_t msgctxt_len = strlen (msgctxt);
416 size_t msgid_len = strlen (msgid);
417 size_t combined_len = msgctxt_len + 1 + msgid_len;
418 char *combined;
419
420 combined = (char *) xmalloca (combined_len + 1);
421 memcpy (combined, msgctxt, msgctxt_len);
422 combined[msgctxt_len] = MSGCTXT_SEPARATOR;
423 memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1);
424
425 write_java_string (stream, combined);
426
427 freea (combined);
428 }
429 }
430
431
432 /* Write Java code that returns the value for a message. If the message
433 has plural forms, it is an expression of type String[], otherwise it is
434 an expression of type String. */
435 static void
write_java_msgstr(FILE * stream,message_ty * mp)436 write_java_msgstr (FILE *stream, message_ty *mp)
437 {
438 if (mp->msgid_plural != NULL)
439 {
440 bool first;
441 const char *p;
442
443 fprintf (stream, "new java.lang.String[] { ");
444 for (p = mp->msgstr, first = true;
445 p < mp->msgstr + mp->msgstr_len;
446 p += strlen (p) + 1, first = false)
447 {
448 if (!first)
449 fprintf (stream, ", ");
450 write_java_string (stream, p);
451 }
452 fprintf (stream, " }");
453 }
454 else
455 {
456 if (mp->msgstr_len != strlen (mp->msgstr) + 1)
457 abort ();
458
459 write_java_string (stream, mp->msgstr);
460 }
461 }
462
463
464 /* Writes the body of the function which returns the local value for a key
465 named 'msgid'. */
466 static void
write_lookup_code(FILE * stream,unsigned int hashsize,bool collisions)467 write_lookup_code (FILE *stream, unsigned int hashsize, bool collisions)
468 {
469 fprintf (stream, " int hash_val = msgid.hashCode() & 0x7fffffff;\n");
470 fprintf (stream, " int idx = (hash_val %% %d) << 1;\n", hashsize);
471 if (collisions)
472 {
473 fprintf (stream, " {\n");
474 fprintf (stream, " java.lang.Object found = table[idx];\n");
475 fprintf (stream, " if (found == null)\n");
476 fprintf (stream, " return null;\n");
477 fprintf (stream, " if (msgid.equals(found))\n");
478 fprintf (stream, " return table[idx + 1];\n");
479 fprintf (stream, " }\n");
480 fprintf (stream, " int incr = ((hash_val %% %d) + 1) << 1;\n",
481 hashsize - 2);
482 fprintf (stream, " for (;;) {\n");
483 fprintf (stream, " idx += incr;\n");
484 fprintf (stream, " if (idx >= %d)\n", 2 * hashsize);
485 fprintf (stream, " idx -= %d;\n", 2 * hashsize);
486 fprintf (stream, " java.lang.Object found = table[idx];\n");
487 fprintf (stream, " if (found == null)\n");
488 fprintf (stream, " return null;\n");
489 fprintf (stream, " if (msgid.equals(found))\n");
490 fprintf (stream, " return table[idx + 1];\n");
491 fprintf (stream, " }\n");
492 }
493 else
494 {
495 fprintf (stream, " java.lang.Object found = table[idx];\n");
496 fprintf (stream, " if (found != null && msgid.equals(found))\n");
497 fprintf (stream, " return table[idx + 1];\n");
498 fprintf (stream, " return null;\n");
499 }
500 }
501
502
503 /* Tests whether a plural expression, evaluated according to the C rules,
504 can only produce the values 0 and 1. */
505 static bool
is_expression_boolean(struct expression * exp)506 is_expression_boolean (struct expression *exp)
507 {
508 switch (exp->operation)
509 {
510 case var:
511 case mult:
512 case divide:
513 case module:
514 case plus:
515 case minus:
516 return false;
517 case lnot:
518 case less_than:
519 case greater_than:
520 case less_or_equal:
521 case greater_or_equal:
522 case equal:
523 case not_equal:
524 case land:
525 case lor:
526 return true;
527 case num:
528 return (exp->val.num == 0 || exp->val.num == 1);
529 case qmop:
530 return is_expression_boolean (exp->val.args[1])
531 && is_expression_boolean (exp->val.args[2]);
532 default:
533 abort ();
534 }
535 }
536
537
538 /* Write Java code that evaluates a plural expression according to the C rules.
539 The variable is called 'n'. */
540 static void
write_java_expression(FILE * stream,const struct expression * exp,bool as_boolean)541 write_java_expression (FILE *stream, const struct expression *exp, bool as_boolean)
542 {
543 /* We use parentheses everywhere. This frees us from tracking the priority
544 of arithmetic operators. */
545 if (as_boolean)
546 {
547 /* Emit a Java expression of type 'boolean'. */
548 switch (exp->operation)
549 {
550 case num:
551 fprintf (stream, "%s", exp->val.num ? "true" : "false");
552 return;
553 case lnot:
554 fprintf (stream, "(!");
555 write_java_expression (stream, exp->val.args[0], true);
556 fprintf (stream, ")");
557 return;
558 case less_than:
559 fprintf (stream, "(");
560 write_java_expression (stream, exp->val.args[0], false);
561 fprintf (stream, " < ");
562 write_java_expression (stream, exp->val.args[1], false);
563 fprintf (stream, ")");
564 return;
565 case greater_than:
566 fprintf (stream, "(");
567 write_java_expression (stream, exp->val.args[0], false);
568 fprintf (stream, " > ");
569 write_java_expression (stream, exp->val.args[1], false);
570 fprintf (stream, ")");
571 return;
572 case less_or_equal:
573 fprintf (stream, "(");
574 write_java_expression (stream, exp->val.args[0], false);
575 fprintf (stream, " <= ");
576 write_java_expression (stream, exp->val.args[1], false);
577 fprintf (stream, ")");
578 return;
579 case greater_or_equal:
580 fprintf (stream, "(");
581 write_java_expression (stream, exp->val.args[0], false);
582 fprintf (stream, " >= ");
583 write_java_expression (stream, exp->val.args[1], false);
584 fprintf (stream, ")");
585 return;
586 case equal:
587 fprintf (stream, "(");
588 write_java_expression (stream, exp->val.args[0], false);
589 fprintf (stream, " == ");
590 write_java_expression (stream, exp->val.args[1], false);
591 fprintf (stream, ")");
592 return;
593 case not_equal:
594 fprintf (stream, "(");
595 write_java_expression (stream, exp->val.args[0], false);
596 fprintf (stream, " != ");
597 write_java_expression (stream, exp->val.args[1], false);
598 fprintf (stream, ")");
599 return;
600 case land:
601 fprintf (stream, "(");
602 write_java_expression (stream, exp->val.args[0], true);
603 fprintf (stream, " && ");
604 write_java_expression (stream, exp->val.args[1], true);
605 fprintf (stream, ")");
606 return;
607 case lor:
608 fprintf (stream, "(");
609 write_java_expression (stream, exp->val.args[0], true);
610 fprintf (stream, " || ");
611 write_java_expression (stream, exp->val.args[1], true);
612 fprintf (stream, ")");
613 return;
614 case qmop:
615 if (is_expression_boolean (exp->val.args[1])
616 && is_expression_boolean (exp->val.args[2]))
617 {
618 fprintf (stream, "(");
619 write_java_expression (stream, exp->val.args[0], true);
620 fprintf (stream, " ? ");
621 write_java_expression (stream, exp->val.args[1], true);
622 fprintf (stream, " : ");
623 write_java_expression (stream, exp->val.args[2], true);
624 fprintf (stream, ")");
625 return;
626 }
627 /*FALLTHROUGH*/
628 case var:
629 case mult:
630 case divide:
631 case module:
632 case plus:
633 case minus:
634 fprintf (stream, "(");
635 write_java_expression (stream, exp, false);
636 fprintf (stream, " != 0)");
637 return;
638 default:
639 abort ();
640 }
641 }
642 else
643 {
644 /* Emit a Java expression of type 'long'. */
645 switch (exp->operation)
646 {
647 case var:
648 fprintf (stream, "n");
649 return;
650 case num:
651 fprintf (stream, "%lu", exp->val.num);
652 return;
653 case mult:
654 fprintf (stream, "(");
655 write_java_expression (stream, exp->val.args[0], false);
656 fprintf (stream, " * ");
657 write_java_expression (stream, exp->val.args[1], false);
658 fprintf (stream, ")");
659 return;
660 case divide:
661 fprintf (stream, "(");
662 write_java_expression (stream, exp->val.args[0], false);
663 fprintf (stream, " / ");
664 write_java_expression (stream, exp->val.args[1], false);
665 fprintf (stream, ")");
666 return;
667 case module:
668 fprintf (stream, "(");
669 write_java_expression (stream, exp->val.args[0], false);
670 fprintf (stream, " %% ");
671 write_java_expression (stream, exp->val.args[1], false);
672 fprintf (stream, ")");
673 return;
674 case plus:
675 fprintf (stream, "(");
676 write_java_expression (stream, exp->val.args[0], false);
677 fprintf (stream, " + ");
678 write_java_expression (stream, exp->val.args[1], false);
679 fprintf (stream, ")");
680 return;
681 case minus:
682 fprintf (stream, "(");
683 write_java_expression (stream, exp->val.args[0], false);
684 fprintf (stream, " - ");
685 write_java_expression (stream, exp->val.args[1], false);
686 fprintf (stream, ")");
687 return;
688 case qmop:
689 fprintf (stream, "(");
690 write_java_expression (stream, exp->val.args[0], true);
691 fprintf (stream, " ? ");
692 write_java_expression (stream, exp->val.args[1], false);
693 fprintf (stream, " : ");
694 write_java_expression (stream, exp->val.args[2], false);
695 fprintf (stream, ")");
696 return;
697 case lnot:
698 case less_than:
699 case greater_than:
700 case less_or_equal:
701 case greater_or_equal:
702 case equal:
703 case not_equal:
704 case land:
705 case lor:
706 fprintf (stream, "(");
707 write_java_expression (stream, exp, true);
708 fprintf (stream, " ? 1 : 0)");
709 return;
710 default:
711 abort ();
712 }
713 }
714 }
715
716
717 /* Write the Java initialization statements for the Java 1.1.x case,
718 for items j, start_index <= j < end_index. */
719 static void
write_java1_init_statements(FILE * stream,message_list_ty * mlp,size_t start_index,size_t end_index)720 write_java1_init_statements (FILE *stream, message_list_ty *mlp,
721 size_t start_index, size_t end_index)
722 {
723 size_t j;
724
725 for (j = start_index; j < end_index; j++)
726 {
727 fprintf (stream, " t.put(");
728 write_java_msgid (stream, mlp->item[j]);
729 fprintf (stream, ",");
730 write_java_msgstr (stream, mlp->item[j]);
731 fprintf (stream, ");\n");
732 }
733 }
734
735
736 /* Write the Java initialization statements for the Java 2 case,
737 for items j, start_index <= j < end_index. */
738 static void
write_java2_init_statements(FILE * stream,message_list_ty * mlp,const struct table_item * table_items,size_t start_index,size_t end_index)739 write_java2_init_statements (FILE *stream, message_list_ty *mlp,
740 const struct table_item *table_items,
741 size_t start_index, size_t end_index)
742 {
743 size_t j;
744
745 for (j = start_index; j < end_index; j++)
746 {
747 const struct table_item *ti = &table_items[j];
748
749 fprintf (stream, " t[%d] = ", 2 * ti->index);
750 write_java_msgid (stream, ti->mp);
751 fprintf (stream, ";\n");
752 fprintf (stream, " t[%d] = ", 2 * ti->index + 1);
753 write_java_msgstr (stream, ti->mp);
754 fprintf (stream, ";\n");
755 }
756 }
757
758
759 /* Write the Java code for the ResourceBundle subclass to the given stream.
760 Note that we use fully qualified class names and no "import" statements,
761 because applications can have their own classes called X.Y.ResourceBundle
762 or X.Y.String. */
763 static void
write_java_code(FILE * stream,const char * class_name,message_list_ty * mlp,bool assume_java2)764 write_java_code (FILE *stream, const char *class_name, message_list_ty *mlp,
765 bool assume_java2)
766 {
767 const char *last_dot;
768 unsigned int plurals;
769 size_t j;
770
771 fprintf (stream,
772 "/* Automatically generated by GNU msgfmt. Do not modify! */\n");
773 last_dot = strrchr (class_name, '.');
774 if (last_dot != NULL)
775 {
776 fprintf (stream, "package ");
777 fwrite (class_name, 1, last_dot - class_name, stream);
778 fprintf (stream, ";\npublic class %s", last_dot + 1);
779 }
780 else
781 fprintf (stream, "public class %s", class_name);
782 fprintf (stream, " extends java.util.ResourceBundle {\n");
783
784 /* Determine whether there are plural messages. */
785 plurals = 0;
786 for (j = 0; j < mlp->nitems; j++)
787 if (mlp->item[j]->msgid_plural != NULL)
788 plurals++;
789
790 if (assume_java2)
791 {
792 unsigned int hashsize;
793 bool collisions;
794 struct table_item *table_items;
795 const char *table_eltype;
796
797 /* Determine the hash table size and whether it leads to collisions. */
798 hashsize = compute_hashsize (mlp, &collisions);
799
800 /* Determines which indices in the table contain a message. The others
801 are null. */
802 table_items = compute_table_items (mlp, hashsize);
803
804 /* Emit the table of pairs (msgid, msgstr). If there are plurals,
805 it is of type Object[], otherwise of type String[]. We use a static
806 code block because that makes less code: The Java compilers also
807 generate code for the 'null' entries, which is dumb. */
808 table_eltype = (plurals ? "java.lang.Object" : "java.lang.String");
809 fprintf (stream, " private static final %s[] table;\n", table_eltype);
810 {
811 /* With the Sun javac compiler, each assignment takes 5 to 8 bytes
812 of bytecode, therefore for each message, up to 16 bytes are needed.
813 Since the bytecode of every method, including the <clinit> method
814 that contains the static initializers, is limited to 64 KB, only ca,
815 65536 / 16 = 4096 messages can be initialized in a single method.
816 Account for other Java compilers and for plurals by limiting it to
817 1000. */
818 const size_t max_items_per_method = 1000;
819
820 if (mlp->nitems > max_items_per_method)
821 {
822 unsigned int k;
823 size_t start_j;
824 size_t end_j;
825
826 for (k = 0, start_j = 0, end_j = start_j + max_items_per_method;
827 start_j < mlp->nitems;
828 k++, start_j = end_j, end_j = start_j + max_items_per_method)
829 {
830 fprintf (stream, " static void clinit_part_%u (%s[] t) {\n",
831 k, table_eltype);
832 write_java2_init_statements (stream, mlp, table_items,
833 start_j, MIN (end_j, mlp->nitems));
834 fprintf (stream, " }\n");
835 }
836 }
837 fprintf (stream, " static {\n");
838 fprintf (stream, " %s[] t = new %s[%d];\n", table_eltype,
839 table_eltype, 2 * hashsize);
840 if (mlp->nitems > max_items_per_method)
841 {
842 unsigned int k;
843 size_t start_j;
844
845 for (k = 0, start_j = 0;
846 start_j < mlp->nitems;
847 k++, start_j += max_items_per_method)
848 fprintf (stream, " clinit_part_%u(t);\n", k);
849 }
850 else
851 write_java2_init_statements (stream, mlp, table_items,
852 0, mlp->nitems);
853 fprintf (stream, " table = t;\n");
854 fprintf (stream, " }\n");
855 }
856
857 /* Emit the msgid_plural strings. Only used by msgunfmt. */
858 if (plurals)
859 {
860 bool first;
861 fprintf (stream, " public static final java.lang.String[] get_msgid_plural_table () {\n");
862 fprintf (stream, " return new java.lang.String[] { ");
863 first = true;
864 for (j = 0; j < mlp->nitems; j++)
865 {
866 struct table_item *ti = &table_items[j];
867 if (ti->mp->msgid_plural != NULL)
868 {
869 if (!first)
870 fprintf (stream, ", ");
871 write_java_string (stream, ti->mp->msgid_plural);
872 first = false;
873 }
874 }
875 fprintf (stream, " };\n");
876 fprintf (stream, " }\n");
877 }
878
879 if (plurals)
880 {
881 /* Emit the lookup function. It is a common subroutine for
882 handleGetObject and ngettext. */
883 fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n");
884 write_lookup_code (stream, hashsize, collisions);
885 fprintf (stream, " }\n");
886 }
887
888 /* Emit the handleGetObject function. It is declared abstract in
889 ResourceBundle. It implements a local version of gettext. */
890 fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n");
891 if (plurals)
892 {
893 fprintf (stream, " java.lang.Object value = lookup(msgid);\n");
894 fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n");
895 }
896 else
897 write_lookup_code (stream, hashsize, collisions);
898 fprintf (stream, " }\n");
899
900 /* Emit the getKeys function. It is declared abstract in ResourceBundle.
901 The inner class is not avoidable. */
902 fprintf (stream, " public java.util.Enumeration getKeys () {\n");
903 fprintf (stream, " return\n");
904 fprintf (stream, " new java.util.Enumeration() {\n");
905 fprintf (stream, " private int idx = 0;\n");
906 fprintf (stream, " { while (idx < %d && table[idx] == null) idx += 2; }\n",
907 2 * hashsize);
908 fprintf (stream, " public boolean hasMoreElements () {\n");
909 fprintf (stream, " return (idx < %d);\n", 2 * hashsize);
910 fprintf (stream, " }\n");
911 fprintf (stream, " public java.lang.Object nextElement () {\n");
912 fprintf (stream, " java.lang.Object key = table[idx];\n");
913 fprintf (stream, " do idx += 2; while (idx < %d && table[idx] == null);\n",
914 2 * hashsize);
915 fprintf (stream, " return key;\n");
916 fprintf (stream, " }\n");
917 fprintf (stream, " };\n");
918 fprintf (stream, " }\n");
919 }
920 else
921 {
922 /* Java 1.1.x uses a different hash function. If compatibility with
923 this Java version is required, the hash table must be built at run time,
924 not at compile time. */
925 fprintf (stream, " private static final java.util.Hashtable table;\n");
926 {
927 /* With the Sun javac compiler, each 'put' call takes 9 to 11 bytes
928 of bytecode, therefore for each message, up to 11 bytes are needed.
929 Since the bytecode of every method, including the <clinit> method
930 that contains the static initializers, is limited to 64 KB, only ca,
931 65536 / 11 = 5958 messages can be initialized in a single method.
932 Account for other Java compilers and for plurals by limiting it to
933 1500. */
934 const size_t max_items_per_method = 1500;
935
936 if (mlp->nitems > max_items_per_method)
937 {
938 unsigned int k;
939 size_t start_j;
940 size_t end_j;
941
942 for (k = 0, start_j = 0, end_j = start_j + max_items_per_method;
943 start_j < mlp->nitems;
944 k++, start_j = end_j, end_j = start_j + max_items_per_method)
945 {
946 fprintf (stream, " static void clinit_part_%u (java.util.Hashtable t) {\n",
947 k);
948 write_java1_init_statements (stream, mlp,
949 start_j, MIN (end_j, mlp->nitems));
950 fprintf (stream, " }\n");
951 }
952 }
953 fprintf (stream, " static {\n");
954 fprintf (stream, " java.util.Hashtable t = new java.util.Hashtable();\n");
955 if (mlp->nitems > max_items_per_method)
956 {
957 unsigned int k;
958 size_t start_j;
959
960 for (k = 0, start_j = 0;
961 start_j < mlp->nitems;
962 k++, start_j += max_items_per_method)
963 fprintf (stream, " clinit_part_%u(t);\n", k);
964 }
965 else
966 write_java1_init_statements (stream, mlp, 0, mlp->nitems);
967 fprintf (stream, " table = t;\n");
968 fprintf (stream, " }\n");
969 }
970
971 /* Emit the msgid_plural strings. Only used by msgunfmt. */
972 if (plurals)
973 {
974 fprintf (stream, " public static final java.util.Hashtable get_msgid_plural_table () {\n");
975 fprintf (stream, " java.util.Hashtable p = new java.util.Hashtable();\n");
976 for (j = 0; j < mlp->nitems; j++)
977 if (mlp->item[j]->msgid_plural != NULL)
978 {
979 fprintf (stream, " p.put(");
980 write_java_msgid (stream, mlp->item[j]);
981 fprintf (stream, ",");
982 write_java_string (stream, mlp->item[j]->msgid_plural);
983 fprintf (stream, ");\n");
984 }
985 fprintf (stream, " return p;\n");
986 fprintf (stream, " }\n");
987 }
988
989 if (plurals)
990 {
991 /* Emit the lookup function. It is a common subroutine for
992 handleGetObject and ngettext. */
993 fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n");
994 fprintf (stream, " return table.get(msgid);\n");
995 fprintf (stream, " }\n");
996 }
997
998 /* Emit the handleGetObject function. It is declared abstract in
999 ResourceBundle. It implements a local version of gettext. */
1000 fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n");
1001 if (plurals)
1002 {
1003 fprintf (stream, " java.lang.Object value = table.get(msgid);\n");
1004 fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n");
1005 }
1006 else
1007 fprintf (stream, " return table.get(msgid);\n");
1008 fprintf (stream, " }\n");
1009
1010 /* Emit the getKeys function. It is declared abstract in
1011 ResourceBundle. */
1012 fprintf (stream, " public java.util.Enumeration getKeys () {\n");
1013 fprintf (stream, " return table.keys();\n");
1014 fprintf (stream, " }\n");
1015 }
1016
1017 /* Emit the pluralEval function. It is a subroutine for ngettext. */
1018 if (plurals)
1019 {
1020 message_ty *header_entry;
1021 const struct expression *plural;
1022 unsigned long int nplurals;
1023
1024 header_entry = message_list_search (mlp, NULL, "");
1025 extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
1026 &plural, &nplurals);
1027
1028 fprintf (stream, " public static long pluralEval (long n) {\n");
1029 fprintf (stream, " return ");
1030 write_java_expression (stream, plural, false);
1031 fprintf (stream, ";\n");
1032 fprintf (stream, " }\n");
1033 }
1034
1035 /* Emit the getParent function. It is a subroutine for ngettext. */
1036 fprintf (stream, " public java.util.ResourceBundle getParent () {\n");
1037 fprintf (stream, " return parent;\n");
1038 fprintf (stream, " }\n");
1039
1040 fprintf (stream, "}\n");
1041 }
1042
1043
1044 int
msgdomain_write_java(message_list_ty * mlp,const char * canon_encoding,const char * resource_name,const char * locale_name,const char * directory,bool assume_java2,bool output_source)1045 msgdomain_write_java (message_list_ty *mlp, const char *canon_encoding,
1046 const char *resource_name, const char *locale_name,
1047 const char *directory,
1048 bool assume_java2,
1049 bool output_source)
1050 {
1051 int retval;
1052 struct temp_dir *tmpdir;
1053 int ndots;
1054 char *class_name;
1055 char **subdirs;
1056 char *java_file_name;
1057 FILE *java_file;
1058 const char *java_sources[1];
1059 const char *source_dir_name;
1060
1061 /* If no entry for this resource/domain, don't even create the file. */
1062 if (mlp->nitems == 0)
1063 return 0;
1064
1065 retval = 1;
1066
1067 /* Convert the messages to Unicode. */
1068 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
1069
1070 /* Support for "reproducible builds": Delete information that may vary
1071 between builds in the same conditions. */
1072 message_list_delete_header_field (mlp, "POT-Creation-Date:");
1073
1074 if (output_source)
1075 {
1076 tmpdir = NULL;
1077 source_dir_name = directory;
1078 }
1079 else
1080 {
1081 /* Create a temporary directory where we can put the Java file. */
1082 tmpdir = create_temp_dir ("msg", NULL, false);
1083 if (tmpdir == NULL)
1084 goto quit1;
1085 source_dir_name = tmpdir->dir_name;
1086 }
1087
1088 /* Assign a default value to the resource name. */
1089 if (resource_name == NULL)
1090 resource_name = "Messages";
1091
1092 /* Prepare the list of subdirectories. */
1093 ndots = check_resource_name (resource_name);
1094 if (ndots < 0)
1095 {
1096 error (0, 0, _("not a valid Java class name: %s"), resource_name);
1097 goto quit2;
1098 }
1099
1100 if (locale_name != NULL)
1101 class_name = xasprintf ("%s_%s", resource_name, locale_name);
1102 else
1103 class_name = xstrdup (resource_name);
1104
1105 subdirs = (ndots > 0 ? (char **) xmalloca (ndots * sizeof (char *)) : NULL);
1106 {
1107 const char *p;
1108 const char *last_dir;
1109 int i;
1110
1111 last_dir = source_dir_name;
1112 p = resource_name;
1113 for (i = 0; i < ndots; i++)
1114 {
1115 const char *q = strchr (p, '.');
1116 size_t n = q - p;
1117 char *part = (char *) xmalloca (n + 1);
1118 memcpy (part, p, n);
1119 part[n] = '\0';
1120 subdirs[i] = xconcatenated_filename (last_dir, part, NULL);
1121 freea (part);
1122 last_dir = subdirs[i];
1123 p = q + 1;
1124 }
1125
1126 if (locale_name != NULL)
1127 {
1128 char *suffix = xasprintf ("_%s.java", locale_name);
1129 java_file_name = xconcatenated_filename (last_dir, p, suffix);
1130 free (suffix);
1131 }
1132 else
1133 java_file_name = xconcatenated_filename (last_dir, p, ".java");
1134 }
1135
1136 /* If OUTPUT_SOURCE, write the Java file in DIRECTORY and return. */
1137 if (output_source)
1138 {
1139 int i;
1140
1141 for (i = 0; i < ndots; i++)
1142 {
1143 if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0)
1144 {
1145 error (0, errno, _("failed to create \"%s\""), subdirs[i]);
1146 goto quit3;
1147 }
1148 }
1149
1150 java_file = fopen (java_file_name, "w");
1151 if (java_file == NULL)
1152 {
1153 error (0, errno, _("failed to create \"%s\""), java_file_name);
1154 goto quit3;
1155 }
1156
1157 write_java_code (java_file, class_name, mlp, assume_java2);
1158
1159 if (fwriteerror (java_file))
1160 {
1161 error (0, errno, _("error while writing \"%s\" file"),
1162 java_file_name);
1163 goto quit3;
1164 }
1165
1166 retval = 0;
1167 goto quit3;
1168 }
1169
1170 /* Create the subdirectories. This is needed because some older Java
1171 compilers verify that the source of class A.B.C really sits in a
1172 directory whose name ends in /A/B. */
1173 {
1174 int i;
1175
1176 for (i = 0; i < ndots; i++)
1177 {
1178 register_temp_subdir (tmpdir, subdirs[i]);
1179 if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0)
1180 {
1181 error (0, errno, _("failed to create \"%s\""), subdirs[i]);
1182 unregister_temp_subdir (tmpdir, subdirs[i]);
1183 goto quit3;
1184 }
1185 }
1186 }
1187
1188 /* Create the Java file. */
1189 register_temp_file (tmpdir, java_file_name);
1190 java_file = fopen_temp (java_file_name, "w", false);
1191 if (java_file == NULL)
1192 {
1193 error (0, errno, _("failed to create \"%s\""), java_file_name);
1194 unregister_temp_file (tmpdir, java_file_name);
1195 goto quit3;
1196 }
1197
1198 write_java_code (java_file, class_name, mlp, assume_java2);
1199
1200 if (fwriteerror_temp (java_file))
1201 {
1202 error (0, errno, _("error while writing \"%s\" file"), java_file_name);
1203 goto quit3;
1204 }
1205
1206 /* Compile the Java file to a .class file.
1207 directory must be non-NULL, because when the -d option is omitted, the
1208 Java compilers create the class files in the source file's directory -
1209 which is in a temporary directory in our case. */
1210 java_sources[0] = java_file_name;
1211 if (compile_java_class (java_sources, 1, NULL, 0, "1.5", "1.6", directory,
1212 true, false, true, verbose > 0))
1213 {
1214 if (!verbose)
1215 error (0, 0,
1216 _("compilation of Java class failed, please try --verbose or set $JAVAC"));
1217 else
1218 error (0, 0,
1219 _("compilation of Java class failed, please try to set $JAVAC"));
1220 goto quit3;
1221 }
1222
1223 retval = 0;
1224
1225 quit3:
1226 {
1227 int i;
1228 free (java_file_name);
1229 for (i = 0; i < ndots; i++)
1230 free (subdirs[i]);
1231 }
1232 freea (subdirs);
1233 free (class_name);
1234 quit2:
1235 if (tmpdir != NULL)
1236 cleanup_temp_dir (tmpdir);
1237 quit1:
1238 return retval;
1239 }
1240