package com.limegroup.gnutella.i18n;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Rebuilds the language files, based on the English one.
*/
class LanguageUpdater {
private static final String MARKER = "# TRANSLATIONS START BELOW.";
private final File lib;
private final Map langs;
private final List englishList;
private boolean verbose = true;
/**
* Constructs a new LanguageUpdater.
*
* @param dir
* @param langs
* @param englishLines
*/
LanguageUpdater(File dir, Map langs, List englishLines) {
this.lib = dir;
this.langs = langs;
this.englishList = englishLines;
removeInitialComments(englishList);
}
/**
* Determines if stuff should be printed.
*
* @param silent
*/
void setSilent(boolean silent) {
verbose = !silent;
}
/**
* Prints a message out if we're being verbose.
*
* @param msg
*/
void print(String msg) {
if (verbose)
System.out.print(msg);
}
/**
* @param msg
*/
void println(String msg) {
if (verbose)
System.out.println(msg);
}
/**
*
*/
void println() {
if (verbose)
System.out.println();
}
/**
* Updates all languages.
*/
void updateAllLanguages() {
for (Iterator i = langs.values().iterator(); i.hasNext();) {
LanguageInfo next = (LanguageInfo) i.next();
// TODO: work with variants.
if (next.isVariant())
continue;
updateLanguage(next);
}
}
/**
* Updates a single language.
*
* @param info
*/
void updateLanguage(LanguageInfo info) {
if (info == null) {
println("Unknown language.");
return;
}
print("Updating language: " + info.getName() + " (" + info.getCode()
+ ")... ");
String filename = info.getFileName();
File f = new File(lib, filename);
if (!f.isFile())
throw new IllegalArgumentException("Invalid info: " + info);
File temp;
BufferedReader reader;
PrintWriter printer;
try {
temp = File.createTempFile("TEMP", info.getCode(), lib);
reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), info.isUTF8() ? "UTF-8"
: "ISO-8859-1"));
printer = new PrintWriter(temp, info.isUTF8() ? "UTF-8"
: "ISO-8859-1");
if (info.isUTF8()) {
reader.mark(1);
if (reader.read() != '\uFEFF')
reader.reset(); /* was not a leading BOM */
printer.print('\uFEFF'); /* force a leading BOM */
}
printInitialComments(printer, reader, info);
printBody(printer, info);
reader.close();
printer.close();
if (isDifferent(f, temp)) {
println("...changes.");
f.delete();
temp.renameTo(f);
} else {
println("...no changes!");
temp.delete();
}
if (info.isUTF8())
native2ascii(info);
} catch (IOException ioe) {
println("...error! (" + ioe.getMessage() + ")");
}
}
/**
* Home-made native2ascii.
*/
private void native2ascii(LanguageInfo info) {
if (!info.isUTF8())
throw new IllegalArgumentException("requires utf8 language.");
InputStream in = null;
OutputStream out = null;
print("\tConverting to ASCII... ");
try {
in = new BufferedInputStream(
new FileInputStream(info.getFileName()));
in.mark(3);
if (in.read() != 0xEF || in.read() != 0xBB || in.read() != 0xBF)
in.reset();
BufferedReader reader = new BufferedReader(new InputStreamReader(
in, "UTF8"));
out = new BufferedOutputStream(new FileOutputStream(info
.getAlternateFileName()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
out, "ISO-8859-1"));
String read;
while ((read = reader.readLine()) != null) {
writer.write(ascii(read));
writer.newLine();
}
writer.flush();
out.flush();
println("... done!");
} catch (IOException ignored) {
println("... error! (" + ignored.getMessage() + ")");
} finally {
if (in != null)
try {
in.close();
} catch (IOException ignored) {
}
if (out != null)
try {
out.close();
} catch (IOException ignored) {
}
}
}
/**
* Determines if there is any difference between file a & file b.
*/
private boolean isDifferent(File a, File b) {
InputStream ia = null, ib = null;
try {
ia = new BufferedInputStream(new FileInputStream(a));
ib = new BufferedInputStream(new FileInputStream(b));
int c;
while ((c = ia.read()) == ib.read()) {
// if we got here, both got to EOF at same time
if (c == -1)
return false;
}
} catch (IOException ignored) {
} finally {
if (ia != null)
try {
ia.close();
} catch (IOException ignored) {
}
if (ib != null)
try {
ib.close();
} catch (IOException ignored) {
}
}
// if we didn't exit in the loop, a character was different
// or one stream ended before another.
return true;
}
/**
* Writes the body of the bundle.
*/
private void printBody(PrintWriter printer, LanguageInfo info) {
Properties props = info.getProperties();
boolean reachedTranslations = false;
for (Iterator i = englishList.iterator(); i.hasNext();) {
Line line = (Line) i.next();
if (MARKER.equals(line.getLine()))
reachedTranslations = true;
if (line.isComment()) {
printer.println(line.getLine());
} else {
String key = line.getKey();
String value = props.getProperty(key);
// always write the English version, so translators
// have a reference point for possibly needing to update
// an older translation.
if (reachedTranslations) {
printer.print("#### ");
printer.print(key);
printer.print("=");
printer.print(escape(line.getValue()));
printer.println();
}
if (value != null
&& line.getBraceCount() == Line.parseBraceCount(value)) {
printer.print(key);
printer.print("=");
printer.println(escape(value));
} else {
printer.print("#? ");
printer.print(key);
printer.print("=");
// only write the non-translated value if we didn't
// above.
if (!reachedTranslations)
printer.print(escape(line.getValue()));
printer.println();
}
}
}
}
/**
* Writes the initial comments from a given file to fos.
*/
private void printInitialComments(PrintWriter printer,
BufferedReader reader, LanguageInfo info) throws IOException {
// TODO: look into initial comments, to see if more information should
// be generated from 'info'.
try {
String read;
// Read through and write the initial lines until we reach a
// non-comment
while ((read = reader.readLine()) != null) {
Line line = new Line(read);
if (!line.isComment())
break;
printer.println(read);
}
} finally {
}
}
/**
* Removes the initial comments from the English properties file.
*/
private void removeInitialComments(List l) {
for (Iterator i = l.iterator(); i.hasNext();) {
Line line = (Line) i.next();
if (line.isComment())
i.remove();
else
break;
}
}
/**
* Returns a string suitable for insertion into a UTF-8 encoded Properties
* file. Some characters will always be escaped.
*/
private String escape(String s) {
final int n = s.length();
StringBuffer sb = new StringBuffer(n);
for (int i = 0; i < n; i++) {
int cp;
if (Character.isSupplementaryCodePoint(cp = s.codePointAt(i)))
i++;
// TODO: use switch(getType(cp)) for more generic handling.
// Whitespace's include all Spacechar's but not non-breaking spaces;
// Spacechar's include all Whitespace's but not C0 controls.
if (Character.isWhitespace(cp)
|| Character.isSpaceChar(cp)
|| Character.isISOControl(cp)
||
// treat isolated surrogates like controls
!Character.isSupplementaryCodePoint(cp)
&& (Character.isLowSurrogate((char) cp) || Character
.isHighSurrogate((char) cp))) {
switch (cp) {
// only ASCII regular SPACE can be left unchanged;
case ' ':
sb.append(' ');
break;
// all other whitespaces and controls must be escaped.
case '\n':
sb.append("\\n");
break;
case '\t':
sb.append("\\t");
break;
case '\f':
sb.append("\\f");
break;
case '\r':
sb.append("\\r");
break;
default:
sb.append(hexUnicode(cp));
}
} else {
// valid non-controls non-whitespaces can be left unchanged.
sb.appendCodePoint(cp);
}
}
return sb.toString();
}
/**
* Converts the input string to ascii, using \\u escapes.
*/
private String ascii(final String s) {
final int n = s.length();
final StringBuffer sb = new StringBuffer(n * 5);
for (int i = 0; i < n; i++) {
int p = s.codePointAt(i);
if (p > 0x00ff || // not Latin-1
p <= 0x001f || // C0 controls
p >= 0x007f && p <= 0x00a0 || // DEL, C1 controls, NBSP
p == 0x00ad) // SHY (Soft Hyphen)
sb.append(hexUnicode(p));
else
sb.appendCodePoint(p);
}
return sb.toString();
}
/**
* Returns the escaped unicode hex representation of the codepoint.
*
* @param cp
* the codepoint to represent; must be in the 17 first planes,
* and not a surrogate.
*/
private String hexUnicode(final int cp) {
if (cp <= 0xffff) {
final String hex = Integer.toHexString(cp);
final StringBuffer sb = new StringBuffer(6);
sb.append("\\u");
for (int j = hex.length(); j < 4; j++)
sb.append('0');
sb.append(hex);
return sb.toString();
}
return new StringBuffer(12).append("\\u").append(
Integer.toHexString(((cp - 0x10000) >> 10) + 0xD800)).append(
"\\u").append(Integer.toHexString((cp & 0x3ff) + 0xDC00))
.toString();
}
}