1 /*
2  * Copyright (C) 2012 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.buildtools;
18 
19 import com.google.i18n.phonenumbers.prefixmapper.PrefixTimeZonesMap;
20 
21 import java.io.BufferedInputStream;
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.ObjectOutputStream;
30 import java.io.OutputStream;
31 import java.nio.charset.Charset;
32 import java.util.SortedMap;
33 import java.util.TreeMap;
34 import java.util.logging.Level;
35 import java.util.logging.Logger;
36 
37 /**
38  * A utility that generates the binary serialization of the prefix/time zones mappings from a
39  * human-readable text file.
40  */
41 public class GenerateTimeZonesMapData {
42   private final File inputTextFile;
43   private static final String MAPPING_DATA_FILE_NAME = "map_data";
44   // The IO Handler used to output the generated binary file.
45   private final AbstractPhonePrefixDataIOHandler ioHandler;
46 
47   private static final Logger logger = Logger.getLogger(GenerateTimeZonesMapData.class.getName());
48 
GenerateTimeZonesMapData(File inputTextFile, AbstractPhonePrefixDataIOHandler ioHandler)49   public GenerateTimeZonesMapData(File inputTextFile, AbstractPhonePrefixDataIOHandler ioHandler)
50       throws IOException {
51     this.inputTextFile = inputTextFile;
52     if (!inputTextFile.isFile()) {
53       throw new IOException("The provided input text file does not exist.");
54     }
55     this.ioHandler = ioHandler;
56   }
57 
58   /**
59    * Reads phone prefix data from the provided input stream and returns a SortedMap with the
60    * prefix to time zones mappings.
61    */
62   // @VisibleForTesting
parseTextFile(InputStream input)63   static SortedMap<Integer, String> parseTextFile(InputStream input)
64       throws IOException, RuntimeException {
65     final SortedMap<Integer, String> timeZoneMap = new TreeMap<Integer, String>();
66     BufferedReader bufferedReader =
67         new BufferedReader(new InputStreamReader(
68             new BufferedInputStream(input), Charset.forName("UTF-8")));
69     int lineNumber = 1;
70 
71     for (String line; (line = bufferedReader.readLine()) != null; lineNumber++) {
72       line = line.trim();
73       if (line.length() == 0 || line.startsWith("#")) {
74         continue;
75       }
76       int indexOfPipe = line.indexOf('|');
77       if (indexOfPipe == -1) {
78         throw new RuntimeException(String.format("line %d: malformatted data, expected '|'",
79                                                  lineNumber));
80       }
81       Integer prefix = Integer.parseInt(line.substring(0, indexOfPipe));
82       String timezones = line.substring(indexOfPipe + 1);
83       if (timezones.isEmpty()) {
84         throw new RuntimeException(String.format("line %d: missing time zones", lineNumber));
85       }
86       if (timeZoneMap.put(prefix, timezones) != null) {
87          throw new RuntimeException(String.format("duplicated prefix %d", prefix));
88       }
89     }
90     return timeZoneMap;
91   }
92 
93   /**
94    * Writes the provided phone prefix/time zones map to the provided output stream.
95    *
96    * @throws IOException
97    */
98   // @VisibleForTesting
writeToBinaryFile(SortedMap<Integer, String> sortedMap, OutputStream output)99   static void writeToBinaryFile(SortedMap<Integer, String> sortedMap, OutputStream output)
100       throws IOException {
101     // Build the corresponding PrefixTimeZonesMap and serialize it to the binary format.
102     PrefixTimeZonesMap prefixTimeZonesMap = new PrefixTimeZonesMap();
103     prefixTimeZonesMap.readPrefixTimeZonesMap(sortedMap);
104     ObjectOutputStream objectOutputStream = new ObjectOutputStream(output);
105     prefixTimeZonesMap.writeExternal(objectOutputStream);
106     objectOutputStream.flush();
107   }
108 
109   /**
110    * Runs the prefix to time zones map data generator.
111    *
112    * @throws IOException
113    */
run()114   public void run() throws IOException {
115     FileInputStream fileInputStream = null;
116     FileOutputStream fileOutputStream = null;
117     try {
118       fileInputStream = new FileInputStream(inputTextFile);
119       SortedMap<Integer, String> mappings = parseTextFile(fileInputStream);
120       File outputBinaryFile = ioHandler.createFile(MAPPING_DATA_FILE_NAME);
121       try {
122         fileOutputStream = new FileOutputStream(outputBinaryFile);
123         writeToBinaryFile(mappings, fileOutputStream);
124         ioHandler.addFileToOutput(outputBinaryFile);
125       } finally {
126         ioHandler.closeFile(fileOutputStream);
127       }
128     } catch (RuntimeException e) {
129       logger.log(Level.SEVERE,
130                  "Error processing file " + inputTextFile.getAbsolutePath());
131       throw e;
132     } catch (IOException e) {
133       logger.log(Level.SEVERE, e.getMessage());
134     } finally {
135       ioHandler.closeFile(fileInputStream);
136       ioHandler.closeFile(fileOutputStream);
137       ioHandler.close();
138     }
139     logger.log(Level.INFO, "Time zone data successfully generated.");
140   }
141 }
142