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