Initial copy of buildMetadata

This commit is contained in:
Vlasislav Kashin
2025-07-11 00:49:56 +03:00
parent e75eda86e6
commit 8a42c0ecb5
19 changed files with 4640 additions and 0 deletions

View File

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

View File

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

View File

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