• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             List<TelephonyLookupProtoFile.MobileCountry> mobileCountriesIn =
84                 telephonyLookupIn.getMobileCountriesList();
85 
86             validateNetworks(networksIn, errors);
87             errors.throwIfError("One or more validation errors encountered");
88 
89             validateMobileCountries(mobileCountriesIn, errors);
90             errors.throwIfError("One or more validation errors encountered");
91 
92             TelephonyLookupXmlFile.TelephonyLookup telephonyLookupOut =
93                     createOutputTelephonyLookup(networksIn, mobileCountriesIn);
94             logInfo("Writing " + outputFile);
95             try {
96                 TelephonyLookupXmlFile.write(telephonyLookupOut, outputFile);
97             } catch (XMLStreamException e) {
98                 throw errors.addFatalAndHalt("Unable to write output file", e);
99             }
100         } catch (HaltExecutionException e) {
101             e.printStackTrace();
102             logError("Stopping due to fatal error: " + e.getMessage());
103         } finally {
104             // Report all warnings / errors
105             if (!errors.isEmpty()) {
106                 logInfo("Issues:\n" + errors.asString());
107             }
108         }
109         return !errors.hasError();
110     }
111 
validateNetworks(List<TelephonyLookupProtoFile.Network> networksIn, Errors errors)112     private static void validateNetworks(List<TelephonyLookupProtoFile.Network> networksIn,
113             Errors errors) {
114         errors.pushScope("validateNetworks");
115         try {
116             Set<String> knownIsoCountries = getLowerCaseCountryIsoCodes();
117             Set<String> mccMncSet = new HashSet<>();
118             for (TelephonyLookupProtoFile.Network networkIn : networksIn) {
119                 String mcc = networkIn.getMcc();
120                 if (mcc.length() != 3 || !isAsciiNumeric(mcc)) {
121                     errors.addError("mcc=" + mcc + " must have 3 decimal digits");
122                 }
123 
124                 String mnc = networkIn.getMnc();
125                 if (!(mnc.length() == 2 || mnc.length() == 3) || !isAsciiNumeric(mnc)) {
126                     errors.addError("mnc=" + mnc + " must have 2 or 3 decimal digits");
127                 }
128 
129                 String mccMnc = "" + mcc + mnc;
130                 if (!mccMncSet.add(mccMnc)) {
131                     errors.addError("Duplicate entry for mcc=" + mcc + ", mnc=" + mnc);
132                 }
133 
134                 String countryIsoCode = networkIn.getCountryIsoCode();
135                 String countryIsoCodeLower = countryIsoCode.toLowerCase(Locale.ROOT);
136                 if (!countryIsoCodeLower.equals(countryIsoCode)) {
137                     errors.addError("Country code not lower case: " + countryIsoCode);
138                 }
139 
140                 if (!knownIsoCountries.contains(countryIsoCodeLower)) {
141                     errors.addError("Country code not known: " + countryIsoCode);
142                 }
143             }
144         } finally {
145             errors.popScope();
146         }
147     }
148 
validateMobileCountries( List<TelephonyLookupProtoFile.MobileCountry> mobileCountriesIn, Errors errors)149     private static void validateMobileCountries(
150             List<TelephonyLookupProtoFile.MobileCountry> mobileCountriesIn,
151             Errors errors) {
152         errors.pushScope("validateMobileCountries");
153         try {
154             Set<String> knownIsoCountries = getLowerCaseCountryIsoCodes();
155             Set<String> mccSet = new HashSet<>();
156 
157             if (mobileCountriesIn.isEmpty()) {
158                 errors.addError("No mobile countries found");
159             }
160 
161             for (TelephonyLookupProtoFile.MobileCountry mobileCountryIn : mobileCountriesIn) {
162                 String mcc = mobileCountryIn.getMcc();
163                 if (mcc.length() != 3 || !isAsciiNumeric(mcc)) {
164                     errors.addError("mcc=" + mcc + " must have 3 decimal digits");
165                 }
166 
167                 if (!mccSet.add(mcc)) {
168                     errors.addError("Duplicate entry for mcc=" + mcc);
169                 }
170 
171                 if (mobileCountryIn.getCountryIsoCodesList().isEmpty()) {
172                     errors.addError("Missing countries for mcc=" + mcc);
173                 }
174 
175                 for (String countryIsoCode : mobileCountryIn.getCountryIsoCodesList()) {
176                     String countryIsoCodeLower = countryIsoCode.toLowerCase(Locale.ROOT);
177                     if (!countryIsoCodeLower.equals(countryIsoCode)) {
178                         errors.addError("Country code not lower case: " + countryIsoCode);
179                     }
180 
181                     if (!knownIsoCountries.contains(countryIsoCodeLower)) {
182                         errors.addError("Country code not known: " + countryIsoCode);
183                     }
184                 }
185             }
186         } finally {
187             errors.popScope();
188         }
189     }
190 
isAsciiNumeric(String string)191     private static boolean isAsciiNumeric(String string) {
192         for (int i = 0; i < string.length(); i++) {
193             char character = string.charAt(i);
194             if (character < '0' || character > '9') {
195                 return false;
196             }
197         }
198         return true;
199     }
200 
getLowerCaseCountryIsoCodes()201     private static Set<String> getLowerCaseCountryIsoCodes() {
202         // Use ICU4J's knowledge of ISO codes because we keep that up to date.
203         List<String> knownIsoCountryCodes = Arrays.asList(ULocale.getISOCountries());
204         knownIsoCountryCodes = knownIsoCountryCodes.stream()
205                 .map(x -> x.toLowerCase(Locale.ROOT))
206                 .collect(Collectors.toList());
207         return new HashSet<>(knownIsoCountryCodes);
208     }
209 
createOutputTelephonyLookup( List<TelephonyLookupProtoFile.Network> networksIn, List<TelephonyLookupProtoFile.MobileCountry> mobileCountriesIn)210     private static TelephonyLookupXmlFile.TelephonyLookup createOutputTelephonyLookup(
211             List<TelephonyLookupProtoFile.Network> networksIn,
212             List<TelephonyLookupProtoFile.MobileCountry> mobileCountriesIn) {
213         // Networks
214         List<TelephonyLookupXmlFile.Network> networksOut = new ArrayList<>();
215         for (TelephonyLookupProtoFile.Network networkIn : networksIn) {
216             String mcc = networkIn.getMcc();
217             String mnc = networkIn.getMnc();
218             String countryIsoCode = networkIn.getCountryIsoCode();
219             TelephonyLookupXmlFile.Network networkOut =
220                     new TelephonyLookupXmlFile.Network(mcc, mnc, countryIsoCode);
221             networksOut.add(networkOut);
222         }
223 
224         // Mobile Countries
225         List<TelephonyLookupXmlFile.MobileCountry> mobileCountriesOut = new ArrayList<>();
226         for (TelephonyLookupProtoFile.MobileCountry mobileCountryIn : mobileCountriesIn) {
227             TelephonyLookupXmlFile.MobileCountry mobileCountryOut =
228                     new TelephonyLookupXmlFile.MobileCountry(
229                             mobileCountryIn.getMcc(), mobileCountryIn.getCountryIsoCodesList());
230             mobileCountriesOut.add(mobileCountryOut);
231         }
232 
233         return new TelephonyLookupXmlFile.TelephonyLookup(networksOut, mobileCountriesOut);
234     }
235 
logError(String msg)236     private static void logError(String msg) {
237         System.err.println("E: " + msg);
238     }
239 
logError(String s, Throwable e)240     private static void logError(String s, Throwable e) {
241         logError(s);
242         e.printStackTrace(System.err);
243     }
244 
logInfo(String msg)245     private static void logInfo(String msg) {
246         System.err.println("I: " + msg);
247     }
248 }
249