--- /dev/null
+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;
+ }
+ }
+}