Initial copy of buildMetadata
This commit is contained in:
161
tools/java/rust-build/pom.xml
Normal file
161
tools/java/rust-build/pom.xml
Normal file
@@ -0,0 +1,161 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>tools</artifactId>
|
||||
<groupId>com.google.i18n.phonenumbers</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.google.i18n.phonenumbers.tools</groupId>
|
||||
<artifactId>cpp-build</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<name>Libphonenumber C++ build tools</name>
|
||||
<description>
|
||||
C++ build tools that download dependencies under base/ from the Chromium source repository, and
|
||||
generate the C++ metadata code needed to build the libphonenumber library.
|
||||
</description>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src</sourceDirectory>
|
||||
<testSourceDirectory>test</testSourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Create a directory called 'generated'. -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>1.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>create-generated-directory</id>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<mkdir dir="generated"/>
|
||||
</tasks>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-source</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<!-- Make BuildMetadataFromXml.java available to the source directories. -->
|
||||
<source>../common/src/</source>
|
||||
<!-- Make Phonemetadata.java available to the source directories.
|
||||
BuildMetadataFromXml.java has to work with both
|
||||
tools/java/cpp-build/generated/com/google/i18n/phonenumbers/Phonemetadata.java
|
||||
and java/libphonenumber/src/com/google/i18n/phonenumbers/Phonemetadata.java.
|
||||
TODO: This Phonemetadata.java is generated via a protoc dependency that is not
|
||||
hermetic and may get out of sync with the other one. Make this file hermetic or
|
||||
find another way to enable Travis CI on this build. -->
|
||||
<source>generated/</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- Invoke Protocol Buffers compiler to generate Phonemetadata.java. -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<executable>protoc</executable>
|
||||
<arguments>
|
||||
<argument>--java_out=generated</argument>
|
||||
<argument>../../../resources/phonemetadata.proto</argument>
|
||||
<argument>--proto_path=../../../resources</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<mainClass>com.google.i18n.phonenumbers.EntryPoint</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Build a JAR with its dependencies (protocol buffers and common library). This JAR
|
||||
contains the C++ build tools invoked by CMake during the libphonenumber C++ build. -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<mainClass>com.google.i18n.phonenumbers.EntryPoint</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>3.25.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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 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.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
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
|
||||
* embed metadata directly in a native binary. We link the object resulting from the compilation of
|
||||
* the code emitted by this class with the C++ phonenumber library.
|
||||
*
|
||||
* @author Philippe Liard
|
||||
* @author David Beaumont
|
||||
*/
|
||||
public class BuildMetadataCppFromXml extends Command {
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An immutable options class for parsing and representing the command line options for this
|
||||
* command.
|
||||
*/
|
||||
// @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
|
||||
public String getCommandName() {
|
||||
return "BuildMetadataCppFromXml";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
|
||||
/** 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();
|
||||
}
|
||||
|
||||
// @VisibleForTesting
|
||||
void writePhoneMetadataCollection(
|
||||
String inputFilePath, boolean liteMetadata, OutputStream out) throws IOException, Exception {
|
||||
BuildMetadataFromXml.buildPhoneMetadataCollection(inputFilePath, liteMetadata, false)
|
||||
.writeTo(out);
|
||||
}
|
||||
|
||||
// @VisibleForTesting
|
||||
OutputStream openHeaderStream(File dir, Type type) throws FileNotFoundException {
|
||||
return new FileOutputStream(new File(dir, type + ".h"));
|
||||
}
|
||||
|
||||
// @VisibleForTesting
|
||||
OutputStream openSourceStream(File dir, Type type, Variant variant) throws FileNotFoundException {
|
||||
return new FileOutputStream(new File(dir, variant.getBasename(type) + ".cc"));
|
||||
}
|
||||
|
||||
/** The charset in which our source and header files will be written. */
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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),
|
||||
/** Metadata for short numbers (expected to be written to short_metadata.[h/cc]). */
|
||||
SHORT_NUMBERS("short_metadata", 2013);
|
||||
|
||||
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 if (Type.SHORT_NUMBERS.toString().equalsIgnoreCase(typeName)) {
|
||||
return Type.SHORT_NUMBERS;
|
||||
} 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();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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;
|
||||
|
||||
/**
|
||||
* Entry point class for C++ build tools.
|
||||
*
|
||||
* @author Philippe Liard
|
||||
*/
|
||||
public class EntryPoint {
|
||||
|
||||
public static void main(String[] args) {
|
||||
boolean status = new CommandDispatcher(args, new Command[] {
|
||||
new BuildMetadataCppFromXml()
|
||||
}).start();
|
||||
|
||||
System.exit(status ? 0 : 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.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.File;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 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 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 {
|
||||
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));
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
public MockedCommand(String expectedInputFilePath, boolean expectedLiteMetadata,
|
||||
String expectedOutputDirPath, Type expectedType, Variant expectedVariant) {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user