blob: 72994e1e0b33b87a052253b3575cc8535e0a35ee [file] [log] [blame]
package org.unicode.icu.tool.cldrtoicu;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.function.Function;
import java.util.logging.Formatter;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import org.unicode.cldr.api.CldrDataSupplier;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
/**
* Utility to allow any "mapper" class to trivially support a main method and useful
* logging behaviour to help avoid the need for ad-hoc logging via {@code System.out}.
*
* <p>In most cases a mapping class can just have a {@code main} method like:
* <pre>{@code
* // Arguments: <output-dir> [<log-level>]
* public static void main(String[] args) throws IOException {
* DebugWriter.writeForDebugging(args, MapperClass::process);
* }
* }</pre>
*
* <p>Note however that running this still requires that the {@code CLDR_DIR} system
* property is set.
*/
public final class DebugWriter {
private static final String PACKAGE_ROOT = "org.unicode.icu.tool.cldrtoicu";
private static final String PACKAGE_PREFIX = PACKAGE_ROOT + ".";
/**
* Writes the IcuData generated by the given function using the default {@code CLDR_DIR}
* system property.
*
* <p>This is a helper method to make it easy for each mapper to have its own main
* method for debugging, and it should not be used directly by {@code LdmlConverter}.
*/
public static void writeForDebugging(String[] args, Function<CldrDataSupplier, IcuData> fn)
throws IOException {
writeMultipleForDebugging(args, src -> ImmutableList.of(fn.apply(src)));
}
/**
* Writes the IcuData generated by the given function using the default {@code CLDR_DIR}
* system property.
*
* <p>This is a helper method to make it easy for each mapper to have its own main
* method for debugging, and it should not be used directly by {@code LdmlConverter}.
*/
public static void writeMultipleForDebugging(
String[] args, Function<CldrDataSupplier, Collection<IcuData>> fn)
throws IOException {
String cldrPath = System.getProperty("CLDR_DIR", System.getenv("CLDR_DIR"));
checkState(cldrPath != null,
"required 'CLDR_DIR' system property or environment variable not set");
checkArgument(args.length >= 1, "expected output directory");
Path outDir = Paths.get(args[0]);
String logLevel = (args.length == 2) ? args[1] : "OFF";
String loggerConfig = Joiner.on("\n").join(
"handlers = java.util.logging.ConsoleHandler",
"java.util.logging.ConsoleHandler.level = ALL",
"java.util.logging.ConsoleHandler.encoding = UTF-8",
"java.util.logging.ConsoleHandler.formatter = " + LogFormatter.class.getName(),
"",
PACKAGE_ROOT + ".level = " + logLevel);
LogManager.getLogManager()
.readConfiguration(new ByteArrayInputStream(loggerConfig.getBytes(UTF_8)));
Files.createDirectories(outDir);
CldrDataSupplier src = CldrDataSupplier.forCldrFilesIn(Paths.get(cldrPath));
ImmutableList<String> header = readLinesFromResource("/ldml2icu_header.txt");
for (IcuData icuData : fn.apply(src)) {
IcuTextWriter.writeToFile(icuData, outDir, header, true);
}
}
private static ImmutableList<String> readLinesFromResource(String name) {
try (InputStream in = DebugWriter.class.getResourceAsStream(name)) {
return ImmutableList.copyOf(CharStreams.readLines(new InputStreamReader(in, UTF_8)));
} catch (IOException e) {
throw new RuntimeException("cannot read resource: " + name, e);
}
}
// Format is "<localClass>#<plainMethod>: <message>" since this is a fairly
// small code base and keeping logs concise is helpful.
// This is only public because it has to be reflectively instantiated.
public static final class LogFormatter extends Formatter {
private static final CharMatcher SEPARATORS = CharMatcher.anyOf("$#");
@Override
public String format(LogRecord logRecord) {
String message = String.format("%s#%s: %s\n",
localClassName(logRecord.getSourceClassName()),
plainMethodName(logRecord.getSourceMethodName()),
logRecord.getMessage());
if (logRecord.getThrown() != null) {
message += logRecord.getThrown() + "\n";
}
return message;
}
// Since everything is in the same base package, elide that (if present).
private String localClassName(String className) {
return className.startsWith(PACKAGE_PREFIX)
? className.substring(className.lastIndexOf(".") + 1)
: className;
}
// Trim method names to remove things like lambda prefixes and anonymous
// class suffixes (these add noise to every log and aren't that useful).
private String plainMethodName(String methodName) {
if (methodName.startsWith("lambda$")) {
methodName = methodName.substring("lambda$".length());
}
if (SEPARATORS.matchesAnyOf(methodName)) {
methodName = methodName.substring(0, SEPARATORS.indexIn(methodName));
}
return methodName;
}
}
}