Browse Source

changes to allow for generation of alternate format metadata

pull/567/head
David Beaumont 14 years ago
committed by Mihaela Rosca
parent
commit
62e1ef2e5e
8 changed files with 673 additions and 268 deletions
  1. +32
    -27
      tools/java/common/src/com/google/i18n/phonenumbers/BuildMetadataFromXml.java
  2. +23
    -30
      tools/java/common/test/com/google/i18n/phonenumbers/BuildMetadataFromXmlTest.java
  3. +157
    -193
      tools/java/cpp-build/src/com/google/i18n/phonenumbers/BuildMetadataCppFromXml.java
  4. +193
    -0
      tools/java/cpp-build/src/com/google/i18n/phonenumbers/CppMetadataGenerator.java
  5. BIN
      tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar
  6. +146
    -18
      tools/java/cpp-build/test/com/google/i18n/phonenumbers/BuildMetadataCppFromXmlTest.java
  7. +122
    -0
      tools/java/cpp-build/test/com/google/i18n/phonenumbers/CppMetadataGeneratorTest.java
  8. BIN
      tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar

+ 32
- 27
tools/java/common/src/com/google/i18n/phonenumbers/BuildMetadataFromXml.java View File

@ -44,7 +44,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
*/
public class BuildMetadataFromXml {
private static final Logger LOGGER = Logger.getLogger(BuildMetadataFromXml.class.getName());
private static boolean liteBuild;
// String constants used to fetch the XML nodes and attributes.
private static final String CARRIER_CODE_FORMATTING_RULE = "carrierCodeFormattingRule";
@ -82,15 +81,9 @@ public class BuildMetadataFromXml {
private static final String VOICEMAIL = "voicemail";
private static final String VOIP = "voip";
// @VisibleForTesting
static void setLiteBuild(boolean b) {
liteBuild = b;
}
// Build the PhoneMetadataCollection from the input XML file.
public static PhoneMetadataCollection buildPhoneMetadataCollection(String inputXmlFile,
boolean liteBuild) throws Exception {
BuildMetadataFromXml.liteBuild = liteBuild;
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
File xmlFile = new File(inputXmlFile);
@ -108,7 +101,7 @@ public class BuildMetadataFromXml {
if (territoryElement.hasAttribute("id")) {
regionCode = territoryElement.getAttribute("id");
}
PhoneMetadata metadata = loadCountryMetadata(regionCode, territoryElement);
PhoneMetadata metadata = loadCountryMetadata(regionCode, territoryElement, liteBuild);
metadataCollection.addMetadata(metadata);
}
return metadataCollection.build();
@ -389,7 +382,8 @@ public class BuildMetadataFromXml {
// @VisibleForTesting
static PhoneNumberDesc.Builder processPhoneNumberDescElement(PhoneNumberDesc.Builder generalDesc,
Element countryElement,
String numberType) {
String numberType,
boolean liteBuild) {
NodeList phoneNumberDescList = countryElement.getElementsByTagName(numberType);
PhoneNumberDesc.Builder numberDesc = PhoneNumberDesc.newBuilder();
if (phoneNumberDescList.getLength() == 0 && !isValidNumberType(numberType)) {
@ -423,31 +417,42 @@ public class BuildMetadataFromXml {
}
// @VisibleForTesting
static void loadGeneralDesc(PhoneMetadata.Builder metadata, Element element) {
static void loadGeneralDesc(PhoneMetadata.Builder metadata, Element element, boolean liteBuild) {
PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
generalDesc = processPhoneNumberDescElement(generalDesc, element, GENERAL_DESC);
generalDesc = processPhoneNumberDescElement(generalDesc, element, GENERAL_DESC, liteBuild);
metadata.setGeneralDesc(generalDesc);
metadata.setFixedLine(processPhoneNumberDescElement(generalDesc, element, FIXED_LINE));
metadata.setMobile(processPhoneNumberDescElement(generalDesc, element, MOBILE));
metadata.setTollFree(processPhoneNumberDescElement(generalDesc, element, TOLL_FREE));
metadata.setPremiumRate(processPhoneNumberDescElement(generalDesc, element, PREMIUM_RATE));
metadata.setSharedCost(processPhoneNumberDescElement(generalDesc, element, SHARED_COST));
metadata.setVoip(processPhoneNumberDescElement(generalDesc, element, VOIP));
metadata.setPersonalNumber(processPhoneNumberDescElement(generalDesc, element,
PERSONAL_NUMBER));
metadata.setPager(processPhoneNumberDescElement(generalDesc, element, PAGER));
metadata.setUan(processPhoneNumberDescElement(generalDesc, element, UAN));
metadata.setVoicemail(processPhoneNumberDescElement(generalDesc, element, VOICEMAIL));
metadata.setEmergency(processPhoneNumberDescElement(generalDesc, element, EMERGENCY));
metadata.setNoInternationalDialling(processPhoneNumberDescElement(generalDesc, element,
NO_INTERNATIONAL_DIALLING));
metadata.setFixedLine(
processPhoneNumberDescElement(generalDesc, element, FIXED_LINE, liteBuild));
metadata.setMobile(
processPhoneNumberDescElement(generalDesc, element, MOBILE, liteBuild));
metadata.setTollFree(
processPhoneNumberDescElement(generalDesc, element, TOLL_FREE, liteBuild));
metadata.setPremiumRate(
processPhoneNumberDescElement(generalDesc, element, PREMIUM_RATE, liteBuild));
metadata.setSharedCost(
processPhoneNumberDescElement(generalDesc, element, SHARED_COST, liteBuild));
metadata.setVoip(
processPhoneNumberDescElement(generalDesc, element, VOIP, liteBuild));
metadata.setPersonalNumber(
processPhoneNumberDescElement(generalDesc, element, PERSONAL_NUMBER, liteBuild));
metadata.setPager(
processPhoneNumberDescElement(generalDesc, element, PAGER, liteBuild));
metadata.setUan(
processPhoneNumberDescElement(generalDesc, element, UAN, liteBuild));
metadata.setVoicemail(
processPhoneNumberDescElement(generalDesc, element, VOICEMAIL, liteBuild));
metadata.setEmergency(
processPhoneNumberDescElement(generalDesc, element, EMERGENCY, liteBuild));
metadata.setNoInternationalDialling(
processPhoneNumberDescElement(generalDesc, element, NO_INTERNATIONAL_DIALLING, liteBuild));
metadata.setSameMobileAndFixedLinePattern(
metadata.getMobile().getNationalNumberPattern().equals(
metadata.getFixedLine().getNationalNumberPattern()));
}
public static PhoneMetadata loadCountryMetadata(String regionCode, Element element) {
// @VisibleForTesting
static PhoneMetadata loadCountryMetadata(String regionCode, Element element, boolean liteBuild) {
String nationalPrefix = getNationalPrefix(element);
PhoneMetadata.Builder metadata =
loadTerritoryTagMetadata(regionCode, element, nationalPrefix);
@ -456,7 +461,7 @@ public class BuildMetadataFromXml {
loadAvailableFormats(metadata, element, nationalPrefix.toString(),
nationalPrefixFormattingRule.toString(),
element.hasAttribute(NATIONAL_PREFIX_OPTIONAL_WHEN_FORMATTING));
loadGeneralDesc(metadata, element);
loadGeneralDesc(metadata, element, liteBuild);
return metadata.build();
}
}

+ 23
- 30
tools/java/common/test/com/google/i18n/phonenumbers/BuildMetadataFromXmlTest.java View File

@ -390,7 +390,7 @@ public class BuildMetadataFromXmlTest extends TestCase {
PhoneNumberDesc.Builder phoneNumberDesc;
phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
generalDesc, territoryElement, "invalidType");
generalDesc, territoryElement, "invalidType", false);
assertEquals("NA", phoneNumberDesc.getPossibleNumberPattern());
assertEquals("NA", phoneNumberDesc.getNationalNumberPattern());
}
@ -403,7 +403,7 @@ public class BuildMetadataFromXmlTest extends TestCase {
PhoneNumberDesc.Builder phoneNumberDesc;
phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
generalDesc, territoryElement, "fixedLine");
generalDesc, territoryElement, "fixedLine", false);
assertEquals("\\d{6}", phoneNumberDesc.getPossibleNumberPattern());
}
@ -419,30 +419,23 @@ public class BuildMetadataFromXmlTest extends TestCase {
PhoneNumberDesc.Builder phoneNumberDesc;
phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
generalDesc, territoryElement, "fixedLine");
generalDesc, territoryElement, "fixedLine", false);
assertEquals("\\d{6}", phoneNumberDesc.getPossibleNumberPattern());
}
public void testProcessPhoneNumberDescElementHandlesLiteBuild()
throws ParserConfigurationException, SAXException, IOException {
try {
BuildMetadataFromXml.setLiteBuild(true);
PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
String xmlInput =
"<territory><fixedLine>" +
" <exampleNumber>01 01 01 01</exampleNumber>" +
"</fixedLine></territory>";
Element territoryElement = parseXmlString(xmlInput);
PhoneNumberDesc.Builder phoneNumberDesc;
phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
generalDesc, territoryElement, "fixedLine");
assertEquals("", phoneNumberDesc.getExampleNumber());
} finally {
// Restore the lite build parameter to its default value (false) to avoid potential
// side-effects in other tests.
BuildMetadataFromXml.setLiteBuild(false);
}
PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
String xmlInput =
"<territory><fixedLine>" +
" <exampleNumber>01 01 01 01</exampleNumber>" +
"</fixedLine></territory>";
Element territoryElement = parseXmlString(xmlInput);
PhoneNumberDesc.Builder phoneNumberDesc;
phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
generalDesc, territoryElement, "fixedLine", true);
assertEquals("", phoneNumberDesc.getExampleNumber());
}
public void testProcessPhoneNumberDescOutputsExampleNumberByDefault()
@ -450,13 +443,13 @@ public class BuildMetadataFromXmlTest extends TestCase {
PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
String xmlInput =
"<territory><fixedLine>" +
" <exampleNumber>01 01 01 01</exampleNumber>" +
"</fixedLine></territory>";
" <exampleNumber>01 01 01 01</exampleNumber>" +
"</fixedLine></territory>";
Element territoryElement = parseXmlString(xmlInput);
PhoneNumberDesc.Builder phoneNumberDesc;
phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
generalDesc, territoryElement, "fixedLine");
generalDesc, territoryElement, "fixedLine", false);
assertEquals("01 01 01 01", phoneNumberDesc.getExampleNumber());
}
@ -465,13 +458,13 @@ public class BuildMetadataFromXmlTest extends TestCase {
PhoneNumberDesc.Builder generalDesc = PhoneNumberDesc.newBuilder();
String xmlInput =
"<territory><fixedLine>" +
" <possibleNumberPattern>\t \\d { 6 } </possibleNumberPattern>" +
"</fixedLine></territory>";
" <possibleNumberPattern>\t \\d { 6 } </possibleNumberPattern>" +
"</fixedLine></territory>";
Element countryElement = parseXmlString(xmlInput);
PhoneNumberDesc.Builder phoneNumberDesc;
phoneNumberDesc = BuildMetadataFromXml.processPhoneNumberDescElement(
generalDesc, countryElement, "fixedLine");
generalDesc, countryElement, "fixedLine", false);
assertEquals("\\d{6}", phoneNumberDesc.getPossibleNumberPattern());
}
@ -486,7 +479,7 @@ public class BuildMetadataFromXmlTest extends TestCase {
Element territoryElement = parseXmlString(xmlInput);
PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
// Should set sameMobileAndFixedPattern to true.
BuildMetadataFromXml.loadGeneralDesc(metadata, territoryElement);
BuildMetadataFromXml.loadGeneralDesc(metadata, territoryElement, false);
assertTrue(metadata.isSameMobileAndFixedLinePattern());
}
@ -504,10 +497,10 @@ public class BuildMetadataFromXmlTest extends TestCase {
" <voip><nationalNumberPattern>\\d{8}</nationalNumberPattern></voip>" +
" <uan><nationalNumberPattern>\\d{9}</nationalNumberPattern></uan>" +
" <shortCode><nationalNumberPattern>\\d{10}</nationalNumberPattern></shortCode>" +
"</territory>";
"</territory>";
Element territoryElement = parseXmlString(xmlInput);
PhoneMetadata.Builder metadata = PhoneMetadata.newBuilder();
BuildMetadataFromXml.loadGeneralDesc(metadata, territoryElement);
BuildMetadataFromXml.loadGeneralDesc(metadata, territoryElement, false);
assertEquals("\\d{1}", metadata.getFixedLine().getNationalNumberPattern());
assertEquals("\\d{2}", metadata.getMobile().getNationalNumberPattern());
assertEquals("\\d{3}", metadata.getPager().getNationalNumberPattern());


+ 157
- 193
tools/java/cpp-build/src/com/google/i18n/phonenumbers/BuildMetadataCppFromXml.java View File

@ -16,15 +16,19 @@
package com.google.i18n.phonenumbers;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection;
import com.google.i18n.phonenumbers.CppMetadataGenerator.Type;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class generates the C++ code representation of the provided XML metadata file. It lets us
@ -32,35 +36,112 @@ import java.util.Set;
* the code emitted by this class with the C++ phonenumber library.
*
* @author Philippe Liard
* @author David Beaumont
*/
public class BuildMetadataCppFromXml extends Command {
// File path where the XML input can be found.
private String inputFilePath;
// Output directory where the generated files will be saved.
private String outputDir;
// 'metadata', 'test_metadata' or 'lite_metadata' depending on the value of the last command line
// parameter.
private String baseFilename;
// Whether to generate "lite" metadata or not.
private boolean liteMetadata;
// The binary translation of the XML file is directly written to a byte array output stream
// instead of creating an unnecessary file on the filesystem.
private ByteArrayOutputStream binaryStream = new ByteArrayOutputStream();
// Header (.h) file and implementation (.cc) file output streams.
private FileOutputStream headerFileOutputStream;
private FileOutputStream implFileOutputStream;
private static final Set<String> METADATA_TYPES =
new HashSet<String>(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.
*
* <p>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 <inputXmlFile> <outputDir> ( <type> | test_<type> | lite_<type> )\n" +
" where <type> 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.
* <pre>
* int metadata_size();
* const void* metadata_get();
* </pre>
*/
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:
*
* <pre>
* #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
*
* </pre>
*/
/**
* 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 <inputXmlFile> <outputDir> ( 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");
}

+ 193
- 0
tools/java/cpp-build/src/com/google/i18n/phonenumbers/CppMetadataGenerator.java View File

@ -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_<TYPE>_H_"
private final String headerInclude; // e.g. "phonenumbers/<type>.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();
}

BIN
tools/java/cpp-build/target/cpp-build-1.0-SNAPSHOT-jar-with-dependencies.jar View File


+ 146
- 18
tools/java/cpp-build/test/com/google/i18n/phonenumbers/BuildMetadataCppFromXmlTest.java View File

@ -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);
}
}
}

+ 122
- 0
tools/java/cpp-build/test/com/google/i18n/phonenumbers/CppMetadataGeneratorTest.java View File

@ -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<String> 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<String> 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<String> toLines(String s) throws IOException {
BufferedReader reader = new BufferedReader(new StringReader(s));
List<String> lines = new ArrayList<String>();
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<String> it) {
while (it.hasNext()) {
if (it.next().equals(expected)) {
return true;
}
}
return false;
}
}

BIN
tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar View File


Loading…
Cancel
Save