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