• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (C) 2011 The Libphonenumber Authors
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *  http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */
16 
17 package com.google.i18n.phonenumbers;
18 
19 import com.google.i18n.phonenumbers.CppMetadataGenerator.Type;
20 
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStream;
27 import java.io.OutputStreamWriter;
28 import java.nio.charset.Charset;
29 import java.util.Arrays;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 
33 /**
34  * This class generates the C++ code representation of the provided XML metadata file. It lets us
35  * embed metadata directly in a native binary. We link the object resulting from the compilation of
36  * the code emitted by this class with the C++ phonenumber library.
37  *
38  * @author Philippe Liard
39  * @author David Beaumont
40  */
41 public class BuildMetadataCppFromXml extends Command {
42 
43   /** An enum encapsulating the variations of metadata that we can produce. */
44   public enum Variant {
45     /** The default 'full' variant which contains all the metadata. */
46     FULL("%s"),
47     /** The test variant which contains fake data for tests. */
48     TEST("test_%s"),
49     /**
50      * The lite variant contains the same metadata as the full version but excludes any example
51      * data. This is typically used for clients with space restrictions.
52      */
53     LITE("lite_%s");
54 
55     private final String template;
56 
Variant(String template)57     private Variant(String template) {
58       this.template = template;
59     }
60 
61     /**
62      * Returns the basename of the type by adding the name of the current variant. The basename of
63      * a Type is used to determine the name of the source file in which the metadata is defined.
64      *
65      * <p>Note that when the variant is {@link Variant#FULL} this method just returns the type name.
66      */
getBasename(Type type)67     public String getBasename(Type type) {
68       return String.format(template, type);
69     }
70 
71     /**
72      * Parses metadata variant name. By default (for a name of {@code ""} or {@code null}) we return
73      * {@link Variant#FULL}, otherwise we match against the variant name (either "test" or "lite").
74      */
parse(String variantName)75     public static Variant parse(String variantName) {
76       if ("test".equalsIgnoreCase(variantName)) {
77         return Variant.TEST;
78       } else if ("lite".equalsIgnoreCase(variantName)) {
79         return Variant.LITE;
80       } else if (variantName == null || variantName.length() == 0) {
81         return Variant.FULL;
82       } else {
83         return null;
84       }
85     }
86   }
87 
88   /**
89    * An immutable options class for parsing and representing the command line options for this
90    * command.
91    */
92   // @VisibleForTesting
93   static final class Options {
94     private static final Pattern BASENAME_PATTERN =
95         Pattern.compile("(?:(test|lite)_)?([a-z_]+)");
96 
parse(String commandName, String[] args)97     public static Options parse(String commandName, String[] args) {
98       if (args.length == 4) {
99         String inputXmlFilePath = args[1];
100         String outputDirPath = args[2];
101         Matcher basenameMatcher = BASENAME_PATTERN.matcher(args[3]);
102         if (basenameMatcher.matches()) {
103           Variant variant = Variant.parse(basenameMatcher.group(1));
104           Type type = Type.parse(basenameMatcher.group(2));
105           if (type != null && variant != null) {
106             return new Options(inputXmlFilePath, outputDirPath, type, variant);
107           }
108         }
109       }
110       throw new IllegalArgumentException(String.format(
111           "Usage: %s <inputXmlFile> <outputDir> ( <type> | test_<type> | lite_<type> )\n" +
112           "       where <type> is one of: %s",
113           commandName, Arrays.asList(Type.values())));
114     }
115 
116     // File path where the XML input can be found.
117     private final String inputXmlFilePath;
118     // Output directory where the generated files will be saved.
119     private final String outputDirPath;
120     private final Type type;
121     private final Variant variant;
122 
Options(String inputXmlFilePath, String outputDirPath, Type type, Variant variant)123     private Options(String inputXmlFilePath, String outputDirPath, Type type, Variant variant) {
124       this.inputXmlFilePath = inputXmlFilePath;
125       this.outputDirPath = outputDirPath;
126       this.type = type;
127       this.variant = variant;
128     }
129 
getInputFilePath()130     public String getInputFilePath() {
131       return inputXmlFilePath;
132     }
133 
getOutputDir()134     public String getOutputDir() {
135       return outputDirPath;
136     }
137 
getType()138     public Type getType() {
139       return type;
140     }
141 
getVariant()142     public Variant getVariant() {
143       return variant;
144     }
145   }
146 
147   @Override
getCommandName()148   public String getCommandName() {
149     return "BuildMetadataCppFromXml";
150   }
151 
152   /**
153    * Generates C++ header and source files to represent the metadata specified by this command's
154    * arguments. The metadata XML file is read and converted to a byte array before being written
155    * into a C++ source file as a static data array.
156    *
157    * @return  true if the generation succeeded.
158    */
159   @Override
start()160   public boolean start() {
161     try {
162       Options opt = Options.parse(getCommandName(), getArgs());
163       byte[] data = loadMetadataBytes(opt.getInputFilePath(), opt.getVariant() == Variant.LITE);
164       CppMetadataGenerator metadata = CppMetadataGenerator.create(opt.getType(), data);
165 
166       // TODO: Consider adding checking for correctness of file paths and access.
167       OutputStream headerStream = null;
168       OutputStream sourceStream = null;
169       try {
170         File dir = new File(opt.getOutputDir());
171         headerStream = openHeaderStream(dir, opt.getType());
172         sourceStream = openSourceStream(dir, opt.getType(), opt.getVariant());
173         metadata.outputHeaderFile(new OutputStreamWriter(headerStream, UTF_8));
174         metadata.outputSourceFile(new OutputStreamWriter(sourceStream, UTF_8));
175       } finally {
176         FileUtils.closeFiles(headerStream, sourceStream);
177       }
178       return true;
179     } catch (IOException e) {
180       System.err.println(e.getMessage());
181     } catch (RuntimeException e) {
182       System.err.println(e.getMessage());
183     }
184     return false;
185   }
186 
187   /** Loads the metadata XML file and converts its contents to a byte array. */
loadMetadataBytes(String inputFilePath, boolean liteMetadata)188   private byte[] loadMetadataBytes(String inputFilePath, boolean liteMetadata) {
189     ByteArrayOutputStream out = new ByteArrayOutputStream();
190     try {
191       writePhoneMetadataCollection(inputFilePath, liteMetadata, out);
192     } catch (Exception e) {
193       // We cannot recover from any exceptions thrown here, so promote them to runtime exceptions.
194       throw new RuntimeException(e);
195     } finally {
196       FileUtils.closeFiles(out);
197     }
198     return out.toByteArray();
199   }
200 
201   // @VisibleForTesting
writePhoneMetadataCollection( String inputFilePath, boolean liteMetadata, OutputStream out)202   void writePhoneMetadataCollection(
203       String inputFilePath, boolean liteMetadata, OutputStream out) throws IOException, Exception {
204     BuildMetadataFromXml.buildPhoneMetadataCollection(inputFilePath, liteMetadata, false)
205         .writeTo(out);
206   }
207 
208   // @VisibleForTesting
openHeaderStream(File dir, Type type)209   OutputStream openHeaderStream(File dir, Type type) throws FileNotFoundException {
210     return new FileOutputStream(new File(dir, type + ".h"));
211   }
212 
213   // @VisibleForTesting
openSourceStream(File dir, Type type, Variant variant)214   OutputStream openSourceStream(File dir, Type type, Variant variant) throws FileNotFoundException {
215     return new FileOutputStream(new File(dir, variant.getBasename(type) + ".cc"));
216   }
217 
218   /** The charset in which our source and header files will be written. */
219   private static final Charset UTF_8 = Charset.forName("UTF-8");
220 }
221