(Arrays.asList("metadata", "test_metadata", "lite_metadata"));
-
- private static final int COPYRIGHT_YEAR = 2011;
+
+ /** An enum encapsulating the variations of metadata that we can produce. */
+ public enum Variant {
+ /** The default 'full' variant which contains all the metadata. */
+ FULL("%s"),
+ /** The test variant which contains fake data for tests. */
+ TEST("test_%s"),
+ /**
+ * The lite variant contains the same metadata as the full version but excludes any example
+ * data. This is typically used for clients with space restrictions.
+ */
+ LITE("lite_%s");
+
+ private final String template;
+
+ private Variant(String template) {
+ this.template = template;
+ }
+
+ /**
+ * Returns the basename of the type by adding the name of the current variant. The basename of
+ * a Type is used to determine the name of the source file in which the metadata is defined.
+ *
+ * Note that when the variant is {@link Variant#FULL} this method just returns the type name.
+ */
+ public String getBasename(Type type) {
+ return String.format(template, type);
+ }
+
+ /**
+ * Parses metadata variant name. By default (for a name of {@code ""} or {@code null}) we return
+ * {@link Variant#FULL}, otherwise we match against the variant name (either "test" or "lite").
+ */
+ public static Variant parse(String variantName) {
+ if ("test".equalsIgnoreCase(variantName)) {
+ return Variant.TEST;
+ } else if ("lite".equalsIgnoreCase(variantName)) {
+ return Variant.LITE;
+ } else if (variantName == null || variantName.length() == 0) {
+ return Variant.FULL;
+ } else {
+ return null;
+ }
+ }
+ }
/**
- * Package private setter used to inject the binary stream for testing purpose.
+ * An immutable options class for parsing and representing the command line options for this
+ * command.
*/
- void setBinaryStream(ByteArrayOutputStream stream) {
- this.binaryStream = stream;
+ // @VisibleForTesting
+ static final class Options {
+ private static final Pattern BASENAME_PATTERN =
+ Pattern.compile("(?:(test|lite)_)?([a-z_]+)");
+
+ public static Options parse(String commandName, String[] args) {
+ if (args.length == 4) {
+ String inputXmlFilePath = args[1];
+ String outputDirPath = args[2];
+ Matcher basenameMatcher = BASENAME_PATTERN.matcher(args[3]);
+ if (basenameMatcher.matches()) {
+ Variant variant = Variant.parse(basenameMatcher.group(1));
+ Type type = Type.parse(basenameMatcher.group(2));
+ if (type != null && variant != null) {
+ return new Options(inputXmlFilePath, outputDirPath, type, variant);
+ }
+ }
+ }
+ throw new IllegalArgumentException(String.format(
+ "Usage: %s ( | test_ | lite_ )\n" +
+ " where is one of: %s",
+ commandName, Arrays.asList(Type.values())));
+ }
+
+ // File path where the XML input can be found.
+ private final String inputXmlFilePath;
+ // Output directory where the generated files will be saved.
+ private final String outputDirPath;
+ private final Type type;
+ private final Variant variant;
+
+ private Options(String inputXmlFilePath, String outputDirPath, Type type, Variant variant) {
+ this.inputXmlFilePath = inputXmlFilePath;
+ this.outputDirPath = outputDirPath;
+ this.type = type;
+ this.variant = variant;
+ }
+
+ public String getInputFilePath() {
+ return inputXmlFilePath;
+ }
+
+ public String getOutputDir() {
+ return outputDirPath;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public Variant getVariant() {
+ return variant;
+ }
}
@Override
@@ -69,187 +150,70 @@ public class BuildMetadataCppFromXml extends Command {
}
/**
- * Starts the generation of the code. First it checks parameters from command line. Then it opens
- * all the streams (input and output streams), emits the header and implementation code and
- * finally closes all the streams.
+ * Generates C++ header and source files to represent the metadata specified by this command's
+ * arguments. The metadata XML file is read and converted to a byte array before being written
+ * into a C++ source file as a static data array.
*
* @return true if the generation succeeded.
*/
@Override
public boolean start() {
- if (!parseCommandLine()) {
- return false;
- }
try {
- generateBinaryFromXml();
- openFiles();
- emitHeader();
- emitImplementation();
- } catch (Exception e) {
+ Options opt = Options.parse(getCommandName(), getArgs());
+ byte[] data = loadMetadataBytes(opt.getInputFilePath(), opt.getVariant() == Variant.LITE);
+ CppMetadataGenerator metadata = CppMetadataGenerator.create(opt.getType(), data);
+
+ // TODO: Consider adding checking for correctness of file paths and access.
+ OutputStream headerStream = null;
+ OutputStream sourceStream = null;
+ try {
+ File dir = new File(opt.getOutputDir());
+ headerStream = openHeaderStream(dir, opt.getType());
+ sourceStream = openSourceStream(dir, opt.getType(), opt.getVariant());
+ metadata.outputHeaderFile(new OutputStreamWriter(headerStream, UTF_8));
+ metadata.outputSourceFile(new OutputStreamWriter(sourceStream, UTF_8));
+ } finally {
+ FileUtils.closeFiles(headerStream, sourceStream);
+ }
+ return true;
+ } catch (IOException e) {
+ System.err.println(e.getMessage());
+ } catch (RuntimeException e) {
System.err.println(e.getMessage());
- return false;
- } finally {
- FileUtils.closeFiles(headerFileOutputStream, implFileOutputStream);
}
- return true;
- }
-
- private void generateBinaryFromXml() throws Exception {
- PhoneMetadataCollection collection =
- BuildMetadataFromXml.buildPhoneMetadataCollection(inputFilePath, liteMetadata);
- collection.writeTo(binaryStream);
- }
-
- /**
- * Opens the binary file input stream and the two file output streams used to emit header and
- * implementation code.
- */
- private void openFiles() throws IOException {
- headerFileOutputStream = new FileOutputStream(String.format("%s/metadata.h", outputDir));
- implFileOutputStream = new FileOutputStream(String.format("%s/%s.cc", outputDir, baseFilename));
+ return false;
}
- private void emitNamespacesBeginning(PrintWriter pw) {
- pw.println("namespace i18n {");
- pw.println("namespace phonenumbers {");
+ /** Loads the metadata XML file and converts its contents to a byte array. */
+ private byte[] loadMetadataBytes(String inputFilePath, boolean liteMetadata) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ writePhoneMetadataCollection(inputFilePath, liteMetadata, out);
+ } catch (Exception e) {
+ // We cannot recover from any exceptions thrown here, so promote them to runtime exceptions.
+ throw new RuntimeException(e);
+ } finally {
+ FileUtils.closeFiles(out);
+ }
+ return out.toByteArray();
}
- private void emitNamespacesEnd(PrintWriter pw) {
- pw.println("} // namespace phonenumbers");
- pw.println("} // namespace i18n");
+ // @VisibleForTesting
+ void writePhoneMetadataCollection(
+ String inputFilePath, boolean liteMetadata, OutputStream out) throws IOException, Exception {
+ BuildMetadataFromXml.buildPhoneMetadataCollection(inputFilePath, liteMetadata).writeTo(out);
}
- /**
- * Generates the header file containing the two function prototypes in namespace
- * i18n::phonenumbers.
- *
- * int metadata_size();
- * const void* metadata_get();
- *
- */
- private void emitHeader() throws IOException {
- final PrintWriter pw = new PrintWriter(headerFileOutputStream);
- CopyrightNotice.writeTo(pw, COPYRIGHT_YEAR);
- final String guardName = "I18N_PHONENUMBERS_METADATA_H_";
- pw.println("#ifndef " + guardName);
- pw.println("#define " + guardName);
-
- pw.println();
- emitNamespacesBeginning(pw);
- pw.println();
-
- pw.println("int metadata_size();");
- pw.println("const void* metadata_get();");
- pw.println();
-
- emitNamespacesEnd(pw);
- pw.println();
-
- pw.println("#endif // " + guardName);
- pw.close();
+ // @VisibleForTesting
+ OutputStream openHeaderStream(File dir, Type type) throws FileNotFoundException {
+ return new FileOutputStream(new File(dir, type + ".h"));
}
- /**
- * The next two methods generate the implementation file (.cc) containing the file data and the
- * two function implementations:
- *
- *
- * #include "X.h"
- *
- * namespace i18n {
- * namespace phonenumbers {
- *
- * namespace {
- * const unsigned char[] data = { .... };
- * } // namespace
- *
- * const void* metadata_get() {
- * return data;
- * }
- *
- * int metadata_size() {
- * return sizeof(data) / sizeof(data[0]);
- * }
- *
- * } // namespace phonenumbers
- * } // namespace i18n
- *
- *
- */
-
- /**
- * Emits the C++ code implementation (.cc file) corresponding to the provided XML input file.
- */
- private void emitImplementation() throws IOException {
- final PrintWriter pw = new PrintWriter(implFileOutputStream);
- CopyrightNotice.writeTo(pw, COPYRIGHT_YEAR);
- pw.println("#include \"phonenumbers/metadata.h\"");
- pw.println();
-
- emitNamespacesBeginning(pw);
- pw.println();
-
- pw.println("namespace {");
- pw.print("static const unsigned char data[] = {");
- emitStaticArrayCode(pw);
- pw.println("};");
- pw.println("} // namespace");
-
- pw.println();
- pw.println("int metadata_size() {");
- pw.println(" return sizeof(data) / sizeof(data[0]);");
- pw.println("}");
-
- pw.println();
- pw.println("const void* metadata_get() {");
- pw.println(" return data;");
- pw.println("}");
-
- pw.println();
- emitNamespacesEnd(pw);
-
- pw.close();
+ // @VisibleForTesting
+ OutputStream openSourceStream(File dir, Type type, Variant variant) throws FileNotFoundException {
+ return new FileOutputStream(new File(dir, variant.getBasename(type) + ".cc"));
}
- /**
- * Emits the C++ code corresponding to the provided XML input file into a static byte array.
- */
- void emitStaticArrayCode(PrintWriter pw) throws IOException {
- byte[] buf = binaryStream.toByteArray();
- pw.print("\n ");
-
- for (int i = 0; i < buf.length; i++) {
- String format = "0x%02X";
-
- if (i == buf.length - 1) {
- format += "\n";
- } else if ((i + 1) % 13 == 0) { // 13 bytes per line to have lines of 79 characters.
- format += ",\n ";
- } else {
- format += ", ";
- }
- pw.printf(format, buf[i]);
- }
- pw.flush();
- binaryStream.flush();
- binaryStream.close();
- }
-
- private boolean parseCommandLine() {
- final String[] args = getArgs();
-
- if (args.length != 4 || !METADATA_TYPES.contains(args[3])) {
- System.err.println(String.format(
- "Usage: %s ( metadata | test_metadata | lite_metadata )",
- getCommandName()));
- return false;
- }
- // args[0] is the name of the command.
- inputFilePath = args[1];
- outputDir = args[2];
- baseFilename = args[3];
- liteMetadata = baseFilename.equals("lite_metadata");
-
- return true;
- }
+ /** The charset in which our source and header files will be written. */
+ private static final Charset UTF_8 = Charset.forName("UTF-8");
}
diff --git a/tools/java/cpp-build/src/com/google/i18n/phonenumbers/CppMetadataGenerator.java b/tools/java/cpp-build/src/com/google/i18n/phonenumbers/CppMetadataGenerator.java
new file mode 100644
index 000000000..f1c6b34a3
--- /dev/null
+++ b/tools/java/cpp-build/src/com/google/i18n/phonenumbers/CppMetadataGenerator.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2012 The Libphonenumber Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.Locale;
+
+/**
+ * Encapsulation of binary metadata created from XML to be included as static data in C++ source
+ * files.
+ *
+ * @author David Beaumont
+ * @author Philippe Liard
+ */
+public final class CppMetadataGenerator {
+
+ /**
+ * The metadata type represents the known types of metadata and includes additional information
+ * such as the copyright year. It is expected that the generated files will be named after the
+ * {@link #toString} of their type.
+ */
+ public enum Type {
+ /** The basic phone number metadata (expected to be written to metadata.[h/cc]). */
+ METADATA("metadata", 2011),
+ /** The alternate format metadata (expected to be written to alternate_format.[h/cc]). */
+ ALTERNATE_FORMAT("alternate_format", 2012);
+
+ private final String typeName;
+ private final int copyrightYear;
+
+ private Type(String typeName, int copyrightYear) {
+ this.typeName = typeName;
+ this.copyrightYear = copyrightYear;
+ }
+
+ /** Returns the year in which this metadata type was first introduced. */
+ public int getCopyrightYear() {
+ return copyrightYear;
+ }
+
+ /**
+ * Returns the name of this type for use in C++ source/header files. Use this in preference to
+ * using {@link #name}.
+ */
+ @Override public String toString() {
+ return typeName;
+ }
+
+ /**
+ * Parses the type from a string case-insensitively.
+ *
+ * @return the matching Type instance or null if not matched.
+ */
+ public static Type parse(String typeName) {
+ if (Type.METADATA.toString().equalsIgnoreCase(typeName)) {
+ return Type.METADATA;
+ } else if (Type.ALTERNATE_FORMAT.toString().equalsIgnoreCase(typeName)) {
+ return Type.ALTERNATE_FORMAT;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Creates a metadata instance that can write C++ source and header files to represent this given
+ * byte array as a static unsigned char array. Note that a direct reference to the byte[] is
+ * retained by the newly created CppXmlMetadata instance, so the caller should treat the array as
+ * immutable after making this call.
+ */
+ public static CppMetadataGenerator create(Type type, byte[] data) {
+ return new CppMetadataGenerator(type, data);
+ }
+
+ private final Type type;
+ private final byte[] data;
+ private final String guardName; // e.g. "I18N_PHONENUMBERS__H_"
+ private final String headerInclude; // e.g. "phonenumbers/.h"
+
+ private CppMetadataGenerator(Type type, byte[] data) {
+ this.type = type;
+ this.data = data;
+ this.guardName = createGuardName(type);
+ this.headerInclude = createHeaderInclude(type);
+ }
+
+ /**
+ * Writes the header file for the C++ representation of the metadata to the given writer. Note
+ * that this method does not close the given writer.
+ */
+ public void outputHeaderFile(Writer out) throws IOException {
+ PrintWriter pw = new PrintWriter(out);
+ CopyrightNotice.writeTo(pw, type.getCopyrightYear());
+ pw.println("#ifndef " + guardName);
+ pw.println("#define " + guardName);
+ pw.println();
+ emitNamespaceStart(pw);
+ pw.println();
+ pw.println("int " + type + "_size();");
+ pw.println("const void* " + type + "_get();");
+ pw.println();
+ emitNamespaceEnd(pw);
+ pw.println();
+ pw.println("#endif // " + guardName);
+ pw.flush();
+ }
+
+ /**
+ * Writes the source file for the C++ representation of the metadata, including a static array
+ * containing the data itself, to the given writer. Note that this method does not close the given
+ * writer.
+ */
+ public void outputSourceFile(Writer out) throws IOException {
+ // TODO: Consider outputting a load method to return the parsed proto directly.
+ PrintWriter pw = new PrintWriter(out);
+ CopyrightNotice.writeTo(pw, type.getCopyrightYear());
+ pw.println("#include \"" + headerInclude + "\"");
+ pw.println();
+ emitNamespaceStart(pw);
+ pw.println();
+ pw.println("namespace {");
+ pw.println("static const unsigned char data[] = {");
+ emitStaticArrayData(pw, data);
+ pw.println("};");
+ pw.println("} // namespace");
+ pw.println();
+ pw.println("int " + type + "_size() {");
+ pw.println(" return sizeof(data) / sizeof(data[0]);");
+ pw.println("}");
+ pw.println();
+ pw.println("const void* " + type + "_get() {");
+ pw.println(" return data;");
+ pw.println("}");
+ pw.println();
+ emitNamespaceEnd(pw);
+ pw.flush();
+ }
+
+ private static String createGuardName(Type type) {
+ return String.format("I18N_PHONENUMBERS_%s_H_", type.toString().toUpperCase(Locale.ENGLISH));
+ }
+
+ private static String createHeaderInclude(Type type) {
+ return String.format("phonenumbers/%s.h", type);
+ }
+
+ private static void emitNamespaceStart(PrintWriter pw) {
+ pw.println("namespace i18n {");
+ pw.println("namespace phonenumbers {");
+ }
+
+ private static void emitNamespaceEnd(PrintWriter pw) {
+ pw.println("} // namespace phonenumbers");
+ pw.println("} // namespace i18n");
+ }
+
+ /** Emits the C++ code corresponding to the binary metadata as a static byte array. */
+ // @VisibleForTesting
+ static void emitStaticArrayData(PrintWriter pw, byte[] data) {
+ String separator = " ";
+ for (int i = 0; i < data.length; i++) {
+ pw.print(separator);
+ emitHexByte(pw, data[i]);
+ separator = ((i + 1) % 13 == 0) ? ",\n " : ", ";
+ }
+ pw.println();
+ }
+
+ /** Emits a single byte in the form 0xHH, where H is an upper case hex digit in [0-9A-F]. */
+ private static void emitHexByte(PrintWriter pw, byte v) {
+ pw.print("0x");
+ pw.print(UPPER_HEX[(v & 0xF0) >>> 4]);
+ pw.print(UPPER_HEX[v & 0xF]);
+ }
+
+ private static final char[] UPPER_HEX = "0123456789ABCDEF".toCharArray();
+}
diff --git a/tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar b/tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar
index 2bdd1bd05..a73e47202 100644
Binary files a/tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar and b/tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar differ
diff --git a/tools/java/cpp-build/test/com/google/i18n/phonenumbers/BuildMetadataCppFromXmlTest.java b/tools/java/cpp-build/test/com/google/i18n/phonenumbers/BuildMetadataCppFromXmlTest.java
index e1c5eed63..b1976e80d 100644
--- a/tools/java/cpp-build/test/com/google/i18n/phonenumbers/BuildMetadataCppFromXmlTest.java
+++ b/tools/java/cpp-build/test/com/google/i18n/phonenumbers/BuildMetadataCppFromXmlTest.java
@@ -17,41 +17,169 @@
package com.google.i18n.phonenumbers;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.google.i18n.phonenumbers.BuildMetadataCppFromXml.Options;
+import com.google.i18n.phonenumbers.BuildMetadataCppFromXml.Variant;
+import com.google.i18n.phonenumbers.CppMetadataGenerator.Type;
+
import org.junit.Test;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
+import java.io.File;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
/**
- * Tests the BuildMetadataCppFromXml implementation to make sure it emits the expected code.
+ * Tests the BuildMetadataCppFromXml implementation to make sure it parses command line options and
+ * generates code correctly.
*/
public class BuildMetadataCppFromXmlTest {
+ // Various repeated test strings and data.
+ private static final String IGNORED = "IGNORED";
+ private static final String OUTPUT_DIR = "output/dir";
+ private static final String INPUT_PATH_XML = "input/path.xml";
+ private static final byte[] TEST_DATA =
+ new byte[] { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE };
+ private static final String CPP_TEST_DATA = "0xCA, 0xFE, 0xBA, 0xBE";
+
@Test
- public void emitStaticArrayCode() {
- final int streamSize = 4;
+ public void parseVariant() {
+ assertNull(Variant.parse("xxx"));
+ assertEquals(Variant.FULL, Variant.parse(null));
+ assertEquals(Variant.FULL, Variant.parse(""));
+ assertEquals(Variant.LITE, Variant.parse("lite"));
+ assertEquals(Variant.TEST, Variant.parse("test"));
+ assertEquals(Variant.LITE, Variant.parse("LITE"));
+ assertEquals(Variant.TEST, Variant.parse("Test"));
+ }
+ @Test
+ public void parseBadOptions() {
try {
- ByteArrayOutputStream stream = new ByteArrayOutputStream(streamSize);
+ BuildMetadataCppFromXml.Options.parse("MyCommand", new String[] { IGNORED });
+ fail("Expected exception not thrown");
+ } catch (IllegalArgumentException e) {
+ assertTrue(e.getMessage().contains("MyCommand"));
+ }
+ }
+
+ @Test
+ public void parseGoodOptions() {
+ Options opt = BuildMetadataCppFromXml.Options.parse("MyCommand",
+ new String[] { IGNORED, INPUT_PATH_XML, OUTPUT_DIR, "test_alternate_format" });
+ assertEquals(Type.ALTERNATE_FORMAT, opt.getType());
+ assertEquals(Variant.TEST, opt.getVariant());
+ assertEquals(INPUT_PATH_XML, opt.getInputFilePath());
+ assertEquals(OUTPUT_DIR, opt.getOutputDir());
+ }
+
+ @Test
+ public void generateMetadata() {
+ String[] args = new String[] {
+ IGNORED, INPUT_PATH_XML, OUTPUT_DIR, "metadata" };
+ // Most of the useful asserts are done in the mock class.
+ MockedCommand command = new MockedCommand(
+ INPUT_PATH_XML, false, OUTPUT_DIR, Type.METADATA, Variant.FULL);
+ command.setArgs(args);
+ command.start();
+ // Sanity check the captured data (asserting implicitly that the mocked methods were called).
+ String headerString = command.capturedHeaderFile();
+ assertTrue(headerString.contains("const void* metadata_get()"));
+ assertTrue(headerString.contains("int metadata_size()"));
+ String sourceString = command.capturedSourceFile();
+ assertTrue(sourceString.contains("const void* metadata_get()"));
+ assertTrue(sourceString.contains("int metadata_size()"));
+ assertTrue(sourceString.contains(CPP_TEST_DATA));
+ }
+
+ @Test
+ public void generateLiteMetadata() {
+ String[] args = new String[] {
+ IGNORED, INPUT_PATH_XML, OUTPUT_DIR, "lite_metadata" };
+ // Most of the useful asserts are done in the mock class.
+ MockedCommand command = new MockedCommand(
+ INPUT_PATH_XML, true, OUTPUT_DIR, Type.METADATA, Variant.LITE);
+ command.setArgs(args);
+ command.start();
+ // Sanity check the captured data (asserting implicitly that the mocked methods were called).
+ String headerString = command.capturedHeaderFile();
+ assertTrue(headerString.contains("const void* metadata_get()"));
+ assertTrue(headerString.contains("int metadata_size()"));
+ String sourceString = command.capturedSourceFile();
+ assertTrue(sourceString.contains("const void* metadata_get()"));
+ assertTrue(sourceString.contains("int metadata_size()"));
+ assertTrue(sourceString.contains(CPP_TEST_DATA));
+ }
- stream.write(0xca);
- stream.write(0xfe);
- stream.write(0xba);
- stream.write(0xbe);
+ @Test
+ public void generateAlternateFormat() {
+ String[] args = new String[] {
+ IGNORED, INPUT_PATH_XML, OUTPUT_DIR, "alternate_format" };
+ // Most of the useful asserts are done in the mock class.
+ MockedCommand command = new MockedCommand(
+ INPUT_PATH_XML, false, OUTPUT_DIR, Type.ALTERNATE_FORMAT, Variant.FULL);
+ command.setArgs(args);
+ command.start();
+ // Sanity check the captured data (asserting implicitly that the mocked methods were called).
+ String headerString = command.capturedHeaderFile();
+ assertTrue(headerString.contains("const void* alternate_format_get()"));
+ assertTrue(headerString.contains("int alternate_format_size()"));
+ String sourceString = command.capturedSourceFile();
+ assertTrue(sourceString.contains("const void* alternate_format_get()"));
+ assertTrue(sourceString.contains("int alternate_format_size()"));
+ assertTrue(sourceString.contains(CPP_TEST_DATA));
+ }
- ByteArrayOutputStream result = new ByteArrayOutputStream(streamSize);
- PrintWriter printWriter = new PrintWriter(result);
+ /**
+ * Manually mocked subclass of BuildMetadataCppFromXml which overrides all file related behavior
+ * while asserting the validity of any parameters passed to the mocked methods. After starting
+ * this command, the captured header and source file contents can be retrieved for testing.
+ */
+ static class MockedCommand extends BuildMetadataCppFromXml {
+ private static final Charset UTF_8 = Charset.forName("UTF-8");
+ private final String expectedInputFilePath;
+ private final boolean expectedLiteMetadata;
+ private final String expectedOutputDirPath;
+ private final Type expectedType;
+ private final Variant expectedVariant;
+ private final ByteArrayOutputStream headerOut = new ByteArrayOutputStream();
+ private final ByteArrayOutputStream sourceOut = new ByteArrayOutputStream();
- BuildMetadataCppFromXml buildMetadataCppFromXml = new BuildMetadataCppFromXml();
- buildMetadataCppFromXml.setBinaryStream(stream);
- buildMetadataCppFromXml.emitStaticArrayCode(printWriter);
+ public MockedCommand(String expectedInputFilePath, boolean expectedLiteMetadata,
+ String expectedOutputDirPath, Type expectedType, Variant expectedVariant) {
- assertEquals("\n 0xCA, 0xFE, 0xBA, 0xBE\n", result.toString());
- } catch (IOException e) {
- fail(e.getMessage());
+ this.expectedInputFilePath = expectedInputFilePath;
+ this.expectedLiteMetadata = expectedLiteMetadata;
+ this.expectedOutputDirPath = expectedOutputDirPath;
+ this.expectedType = expectedType;
+ this.expectedVariant = expectedVariant;
+ }
+ @Override void writePhoneMetadataCollection(
+ String inputFilePath, boolean liteMetadata, OutputStream out) throws Exception {
+ assertEquals(expectedInputFilePath, inputFilePath);
+ assertEquals(expectedLiteMetadata, liteMetadata);
+ out.write(TEST_DATA, 0, TEST_DATA.length);
+ }
+ @Override OutputStream openHeaderStream(File dir, Type type) {
+ assertEquals(expectedOutputDirPath, dir.getPath());
+ assertEquals(expectedType, type);
+ return headerOut;
+ }
+ @Override OutputStream openSourceStream(File dir, Type type, Variant variant) {
+ assertEquals(expectedOutputDirPath, dir.getPath());
+ assertEquals(expectedType, type);
+ assertEquals(expectedVariant, variant);
+ return sourceOut;
+ }
+ String capturedHeaderFile() {
+ return new String(headerOut.toByteArray(), UTF_8);
+ }
+ String capturedSourceFile() {
+ return new String(sourceOut.toByteArray(), UTF_8);
}
}
}
diff --git a/tools/java/cpp-build/test/com/google/i18n/phonenumbers/CppMetadataGeneratorTest.java b/tools/java/cpp-build/test/com/google/i18n/phonenumbers/CppMetadataGeneratorTest.java
new file mode 100644
index 000000000..4cd9bee0e
--- /dev/null
+++ b/tools/java/cpp-build/test/com/google/i18n/phonenumbers/CppMetadataGeneratorTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2012 The Libphonenumber Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.i18n.phonenumbers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.i18n.phonenumbers.CppMetadataGenerator.Type;
+
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Tests that the CppXmlMetadata class emits the expected source and header files for metadata.
+ */
+public class CppMetadataGeneratorTest {
+
+ @Test
+ public void emitStaticArrayData() {
+ // 13 bytes per line, so have 16 bytes to test > 1 line (general case).
+ // Use all hex digits in both nibbles to test hex formatting.
+ byte[] data = new byte[] {
+ (byte) 0xF0, (byte) 0xE1, (byte) 0xD2, (byte) 0xC3,
+ (byte) 0xB4, (byte) 0xA5, (byte) 0x96, (byte) 0x87,
+ (byte) 0x78, (byte) 0x69, (byte) 0x5A, (byte) 0x4B,
+ (byte) 0x3C, (byte) 0x2D, (byte) 0x1E, (byte) 0x0F,
+ };
+
+ StringWriter writer = new StringWriter();
+ CppMetadataGenerator.emitStaticArrayData(new PrintWriter(writer), data);
+ assertEquals(
+ " 0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87, 0x78, 0x69, 0x5A, 0x4B, 0x3C,\n" +
+ " 0x2D, 0x1E, 0x0F\n",
+ writer.toString());
+ }
+
+ @Test
+ public void outputHeaderFile() throws IOException {
+ byte[] data = new byte[] { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE };
+ CppMetadataGenerator metadata = CppMetadataGenerator.create(Type.METADATA, data);
+
+ StringWriter writer = new StringWriter();
+ metadata.outputHeaderFile(writer);
+ Iterator lines = toLines(writer.toString()).iterator();
+ // Sanity check that at least some of the expected lines are present.
+ assertTrue(consumeUntil(" * Copyright (C) 2011 The Libphonenumber Authors", lines));
+ assertTrue(consumeUntil("#ifndef I18N_PHONENUMBERS_METADATA_H_", lines));
+ assertTrue(consumeUntil("#define I18N_PHONENUMBERS_METADATA_H_", lines));
+ assertTrue(consumeUntil("namespace i18n {", lines));
+ assertTrue(consumeUntil("namespace phonenumbers {", lines));
+ assertTrue(consumeUntil("int metadata_size();", lines));
+ assertTrue(consumeUntil("const void* metadata_get();", lines));
+ assertTrue(consumeUntil("#endif // I18N_PHONENUMBERS_METADATA_H_", lines));
+ }
+
+ @Test
+ public void outputSourceFile() throws IOException {
+ byte[] data = new byte[] { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE };
+ CppMetadataGenerator metadata = CppMetadataGenerator.create(Type.ALTERNATE_FORMAT, data);
+
+ StringWriter writer = new StringWriter();
+ metadata.outputSourceFile(writer);
+ Iterator lines = toLines(writer.toString()).iterator();
+ // Sanity check that at least some of the expected lines are present.
+ assertTrue(consumeUntil(" * Copyright (C) 2012 The Libphonenumber Authors", lines));
+ assertTrue(consumeUntil("namespace i18n {", lines));
+ assertTrue(consumeUntil("namespace phonenumbers {", lines));
+ assertTrue(consumeUntil("namespace {", lines));
+ assertTrue(consumeUntil("static const unsigned char data[] = {", lines));
+ assertTrue(consumeUntil(" 0xCA, 0xFE, 0xBA, 0xBE", lines));
+ assertTrue(consumeUntil("int alternate_format_size() {", lines));
+ assertTrue(consumeUntil("const void* alternate_format_get() {", lines));
+ }
+
+ /** Converts a string containing newlines into a list of lines. */
+ private static List toLines(String s) throws IOException {
+ BufferedReader reader = new BufferedReader(new StringReader(s));
+ List lines = new ArrayList();
+ for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+ lines.add(line);
+ }
+ return lines;
+ }
+
+ /**
+ * Consumes strings from the given iterator until the expected string is reached (it is also
+ * consumed). If the expected string is not found, the iterator is exhausted and {@code false} is
+ * returned.
+ *
+ * @return true if the expected string was found while consuming the iterator.
+ */
+ private static boolean consumeUntil(String expected, Iterator it) {
+ while (it.hasNext()) {
+ if (it.next().equals(expected)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar b/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar
index 2d63a1132..041695278 100644
Binary files a/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar and b/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar differ