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