1 /*
2  * Copyright (C) 2019 The Android Open Source Project
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 package com.android.libcore.timezone.telephonylookup;
17 
18 import static com.android.libcore.timezone.telephonylookup.TelephonyLookupProtoFileSupport.parseTelephonyLookupTextFile;
19 
20 import com.android.libcore.timezone.telephonylookup.proto.TelephonyLookupProtoFile;
21 import com.android.libcore.timezone.util.Errors;
22 import com.android.libcore.timezone.util.Errors.HaltExecutionException;
23 
24 import com.ibm.icu.util.ULocale;
25 
26 import java.io.IOException;
27 import java.text.ParseException;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Set;
34 import java.util.stream.Collectors;
35 
36 import javax.xml.stream.XMLStreamException;
37 
38 /**
39  * Generates the telephonylookup.xml file using the information from telephonylookup.txt.
40  *
41  * See {@link #main(String[])} for commandline information.
42  */
43 public final class TelephonyLookupGenerator {
44 
45     private final String telephonyLookupProtoFile;
46     private final String outputFile;
47 
48     /**
49      * Executes the generator.
50      *
51      * Positional arguments:
52      * 1: The telephonylookup.txt proto file
53      * 2: the file to generate
54      */
main(String[] args)55     public static void main(String[] args) throws Exception {
56         if (args.length != 2) {
57             System.err.println(
58                     "usage: java " + TelephonyLookupGenerator.class.getName()
59                             + " <input proto file> <output xml file>");
60             System.exit(0);
61         }
62         boolean success = new TelephonyLookupGenerator(args[0], args[1]).execute();
63         System.exit(success ? 0 : 1);
64     }
65 
TelephonyLookupGenerator(String telephonyLookupProtoFile, String outputFile)66     TelephonyLookupGenerator(String telephonyLookupProtoFile, String outputFile) {
67         this.telephonyLookupProtoFile = telephonyLookupProtoFile;
68         this.outputFile = outputFile;
69     }
70 
execute()71     boolean execute() throws IOException {
72         Errors errors = new Errors();
73         try {
74             // Parse the countryzones input file.
75             TelephonyLookupProtoFile.TelephonyLookup telephonyLookupIn;
76             try {
77                 telephonyLookupIn = parseTelephonyLookupTextFile(telephonyLookupProtoFile);
78             } catch (ParseException e) {
79                 throw errors.addFatalAndHalt("Unable to parse " + telephonyLookupProtoFile, e);
80             }
81 
82             List<TelephonyLookupProtoFile.Network> networksIn = telephonyLookupIn.getNetworksList();
83 
84             validateNetworks(networksIn, errors);
85             errors.throwIfError("One or more validation errors encountered");
86 
87             TelephonyLookupXmlFile.TelephonyLookup telephonyLookupOut =
88                     createOutputTelephonyLookup(networksIn);
89             logInfo("Writing " + outputFile);
90             try {
91                 TelephonyLookupXmlFile.write(telephonyLookupOut, outputFile);
92             } catch (XMLStreamException e) {
93                 throw errors.addFatalAndHalt("Unable to write output file", e);
94             }
95         } catch (HaltExecutionException e) {
96             e.printStackTrace();
97             logError("Stopping due to fatal error: " + e.getMessage());
98         } finally {
99             // Report all warnings / errors
100             if (!errors.isEmpty()) {
101                 logInfo("Issues:\n" + errors.asString());
102             }
103         }
104         return !errors.hasError();
105     }
106 
validateNetworks(List<TelephonyLookupProtoFile.Network> networksIn, Errors errors)107     private static void validateNetworks(List<TelephonyLookupProtoFile.Network> networksIn,
108             Errors errors) {
109         errors.pushScope("validateNetworks");
110         try {
111             Set<String> knownIsoCountries = getLowerCaseCountryIsoCodes();
112             Set<String> mccMncSet = new HashSet<>();
113             for (TelephonyLookupProtoFile.Network networkIn : networksIn) {
114                 String mcc = networkIn.getMcc();
115                 if (mcc.length() != 3 || !isAsciiNumeric(mcc)) {
116                     errors.addError("mcc=" + mcc + " must have 3 decimal digits");
117                 }
118 
119                 String mnc = networkIn.getMnc();
120                 if (!(mnc.length() == 2 || mnc.length() == 3) || !isAsciiNumeric(mnc)) {
121                     errors.addError("mnc=" + mnc + " must have 2 or 3 decimal digits");
122                 }
123 
124                 String mccMnc = "" + mcc + mnc;
125                 if (!mccMncSet.add(mccMnc)) {
126                     errors.addError("Duplicate entry for mcc=" + mcc + ", mnc=" + mnc);
127                 }
128 
129                 String countryIsoCode = networkIn.getCountryIsoCode();
130                 String countryIsoCodeLower = countryIsoCode.toLowerCase(Locale.ROOT);
131                 if (!countryIsoCodeLower.equals(countryIsoCode)) {
132                     errors.addError("Country code not lower case: " + countryIsoCode);
133                 }
134 
135                 if (!knownIsoCountries.contains(countryIsoCodeLower)) {
136                     errors.addError("Country code not known: " + countryIsoCode);
137                 }
138             }
139         } finally {
140             errors.popScope();
141         }
142     }
143 
isAsciiNumeric(String string)144     private static boolean isAsciiNumeric(String string) {
145         for (int i = 0; i < string.length(); i++) {
146             char character = string.charAt(i);
147             if (character < '0' || character > '9') {
148                 return false;
149             }
150         }
151         return true;
152     }
153 
getLowerCaseCountryIsoCodes()154     private static Set<String> getLowerCaseCountryIsoCodes() {
155         // Use ICU4J's knowledge of ISO codes because we keep that up to date.
156         List<String> knownIsoCountryCodes = Arrays.asList(ULocale.getISOCountries());
157         knownIsoCountryCodes = knownIsoCountryCodes.stream()
158                 .map(x -> x.toLowerCase(Locale.ROOT))
159                 .collect(Collectors.toList());
160         return new HashSet<>(knownIsoCountryCodes);
161     }
162 
createOutputTelephonyLookup( List<TelephonyLookupProtoFile.Network> networksIn)163     private static TelephonyLookupXmlFile.TelephonyLookup createOutputTelephonyLookup(
164             List<TelephonyLookupProtoFile.Network> networksIn) {
165         List<TelephonyLookupXmlFile.Network> networksOut = new ArrayList<>();
166         for (TelephonyLookupProtoFile.Network networkIn : networksIn) {
167             String mcc = networkIn.getMcc();
168             String mnc = networkIn.getMnc();
169             String countryIsoCode = networkIn.getCountryIsoCode();
170             TelephonyLookupXmlFile.Network networkOut =
171                     new TelephonyLookupXmlFile.Network(mcc, mnc, countryIsoCode);
172             networksOut.add(networkOut);
173         }
174         return new TelephonyLookupXmlFile.TelephonyLookup(networksOut);
175     }
176 
logError(String msg)177     private static void logError(String msg) {
178         System.err.println("E: " + msg);
179     }
180 
logError(String s, Throwable e)181     private static void logError(String s, Throwable e) {
182         logError(s);
183         e.printStackTrace(System.err);
184     }
185 
logInfo(String msg)186     private static void logInfo(String msg) {
187         System.err.println("I: " + msg);
188     }
189 }
190