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.  */
88static int
89check_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.  */
121static unsigned int
122string_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.  */
147static unsigned int
148compute_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
281struct table_item { unsigned int index; message_ty *mp; };
282
283static int
284compare_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.  */
292static struct table_item *
293compute_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.  */
335static void
336write_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.  */
384static void
385write_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'.  */
415static void
416write_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.  */
454static bool
455is_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'.  */
489static void
490write_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.  */
670static void
671write_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
889int
890msgdomain_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 (_("\
921message catalog has context dependent translations\n\
922but 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, _("\
1030compilation 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