1 /* 2 * Copyright (C) 2020 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 package com.google.phonenumbers.migrator; 17 18 import com.google.common.base.CharMatcher; 19 import com.google.common.collect.ImmutableList; 20 import com.google.i18n.phonenumbers.metadata.DigitSequence; 21 import com.google.i18n.phonenumbers.metadata.table.CsvTable; 22 import com.google.i18n.phonenumbers.metadata.table.RangeKey; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.io.InputStreamReader; 26 import java.nio.file.Files; 27 import java.nio.file.Path; 28 import java.util.List; 29 30 /** 31 * Factory class to instantiate {@link MigrationJob} objects. To create a Migration Job, a BCP-47 32 * country code is required as well as a number input (either a single E.164 number or a file 33 * path containing one E.164 number per line). There is also the option for specifying a custom 34 * recipes file as a third parameter to use for migrations instead of the default recipes file. 35 */ 36 public class MigrationFactory { 37 38 // made public for testing purposes 39 public final static String DEFAULT_RECIPES_FILE = "/recipes.csv"; 40 public final static String METADATA_ZIPFILE ="/metadata.zip"; 41 42 /** 43 * Creates a new MigrationJob for a given single E.164 number input (e.g. +4477...) and its 44 * corresponding BCP-47 country code (e.g. 44 for United Kingdom). 45 */ createMigration(String number, String country)46 public static MigrationJob createMigration(String number, String country) throws IOException { 47 DigitSequence countryCode = DigitSequence.of(country); 48 ImmutableList<MigrationEntry> numberRanges = 49 ImmutableList.of(MigrationEntry.create(sanitizeNumberString(number), number)); 50 CsvTable<RangeKey> recipes = importRecipes(MigrationFactory.class 51 .getResourceAsStream(DEFAULT_RECIPES_FILE)); 52 53 MetadataZipFileReader metadata = MetadataZipFileReader.of(MigrationFactory.class 54 .getResourceAsStream(METADATA_ZIPFILE)); 55 CsvTable<RangeKey> ranges = metadata.importCsvTable(countryCode) 56 .orElseThrow(() -> new RuntimeException( 57 "Country code " + countryCode+ " not supported in metadata")); 58 59 return new MigrationJob(numberRanges, countryCode, recipes, 60 ranges, /* exportInvalidMigrations= */false); 61 } 62 63 /** 64 * Returns a MigrationJob instance for a given single E.164 number input, corresponding BCP-47 65 * country code (e.g. 1 for Canada), and custom user recipes.csv file. 66 */ createCustomRecipeMigration(String number, String country, CsvTable<RangeKey> recipes)67 public static MigrationJob createCustomRecipeMigration(String number, 68 String country, 69 CsvTable<RangeKey> recipes) { 70 DigitSequence countryCode = DigitSequence.of(country); 71 ImmutableList<MigrationEntry> numberRanges = 72 ImmutableList.of(MigrationEntry.create(sanitizeNumberString(number), number)); 73 74 return new MigrationJob(numberRanges, countryCode, recipes, 75 /* rangesTable= */null, /* exportInvalidMigrations= */false); 76 } 77 78 /** 79 * Returns a MigrationJob instance for a given file path containing one E.164 80 * number per line (e.g. +4477..., +4478...) along with the corresponding 81 * BCP-47 country code (e.g. 44) that numbers in the file belong to. 82 * All numbers in the file should belong to the same region. 83 */ createMigration(Path file, String country, boolean exportInvalidMigrations)84 public static MigrationJob createMigration(Path file, String country, boolean exportInvalidMigrations) 85 throws IOException { 86 List<String> numbers = Files.readAllLines(file); 87 DigitSequence countryCode = DigitSequence.of(country); 88 ImmutableList.Builder<MigrationEntry> numberRanges = ImmutableList.builder(); 89 90 numbers.forEach(num -> numberRanges.add(MigrationEntry.create(sanitizeNumberString(num), num))); 91 CsvTable<RangeKey> recipes = importRecipes(MigrationFactory.class 92 .getResourceAsStream(DEFAULT_RECIPES_FILE)); 93 94 MetadataZipFileReader metadata = MetadataZipFileReader.of(MigrationFactory.class 95 .getResourceAsStream(METADATA_ZIPFILE)); 96 CsvTable<RangeKey> ranges = metadata.importCsvTable(countryCode) 97 .orElseThrow(() -> new RuntimeException( 98 "Country code " + countryCode+ " not supported in metadata")); 99 100 return new MigrationJob(numberRanges.build(), countryCode, recipes, ranges, exportInvalidMigrations); 101 } 102 103 /** 104 * Returns a MigrationJob instance for a given file path containing one E.164 105 * number per line, corresponding BCP-47 country code, and custom user recipes.csv file. 106 */ createCustomRecipeMigration(Path file, String country, CsvTable<RangeKey> recipes)107 public static MigrationJob createCustomRecipeMigration(Path file, 108 String country, 109 CsvTable<RangeKey> recipes) 110 throws IOException { 111 List<String> numbers = Files.readAllLines(file); 112 DigitSequence countryCode = DigitSequence.of(country); 113 ImmutableList.Builder<MigrationEntry> numberRanges = ImmutableList.builder(); 114 115 numbers.forEach(num -> numberRanges.add(MigrationEntry.create(sanitizeNumberString(num), num))); 116 117 return new MigrationJob(numberRanges.build(), countryCode, recipes, 118 /* rangesTable= */null, /* exportInvalidMigrations= */ false); 119 } 120 121 /** 122 * Returns a MigrationJob instance for a given file path containing one E.164 number per line, corresponding BCP-47 123 * country code, and custom user recipes.csv file. Method needed for migrations using migrator-servlet. 124 */ createCustomRecipeMigration(ImmutableList<String> numbers, String country, CsvTable<RangeKey> recipes)125 public static MigrationJob createCustomRecipeMigration(ImmutableList<String> numbers, 126 String country, 127 CsvTable<RangeKey> recipes) throws IOException { 128 DigitSequence countryCode = DigitSequence.of(country); 129 ImmutableList.Builder<MigrationEntry> numberRanges = ImmutableList.builder(); 130 numbers.forEach(num -> numberRanges.add(MigrationEntry.create(sanitizeNumberString(num), num))); 131 132 return new MigrationJob(numberRanges.build(), countryCode, recipes, 133 /* rangesTable= */null, /* exportInvalidMigrations= */ false); 134 } 135 136 /** 137 * Returns a MigrationJob instance for a given list of E.164 numbers. Method needed for file migrations using migrator 138 * servlet. 139 */ createMigration(ImmutableList<String> numbers, String country, boolean exportInvalidMigrations)140 public static MigrationJob createMigration(ImmutableList<String> numbers, String country, boolean exportInvalidMigrations) 141 throws IOException { 142 DigitSequence countryCode = DigitSequence.of(country); 143 ImmutableList.Builder<MigrationEntry> numberRanges = ImmutableList.builder(); 144 145 numbers.forEach(num -> numberRanges.add(MigrationEntry.create(sanitizeNumberString(num), num))); 146 CsvTable<RangeKey> recipes = importRecipes(MigrationFactory.class 147 .getResourceAsStream(DEFAULT_RECIPES_FILE)); 148 149 MetadataZipFileReader metadata = MetadataZipFileReader.of(MigrationFactory.class 150 .getResourceAsStream(METADATA_ZIPFILE)); 151 CsvTable<RangeKey> ranges = metadata.importCsvTable(countryCode) 152 .orElseThrow(() -> new RuntimeException( 153 "Country code " + countryCode+ " not supported in metadata")); 154 155 return new MigrationJob(numberRanges.build(), countryCode, recipes, ranges, exportInvalidMigrations); 156 } 157 158 /** 159 * Removes spaces and '+' '(' ')' '-' characters expected in E.164 numbers then returns the 160 * {@link DigitSequence} representation of a given number. The method will not remove other 161 * letters or special characters from strings to enable error messages in cases where invalid 162 * numbers are inputted. 163 */ sanitizeNumberString(String number)164 private static DigitSequence sanitizeNumberString(String number) { 165 CharMatcher matcher = CharMatcher.anyOf("-+()").or(CharMatcher.whitespace()); 166 return DigitSequence.of(matcher.removeFrom(number)); 167 } 168 169 /** 170 * Returns the {@link CsvTable} for a given recipes file path if present. 171 * Made public for testing purposes. 172 */ importRecipes(InputStream recipesFile)173 public static CsvTable<RangeKey> importRecipes(InputStream recipesFile) throws IOException { 174 InputStreamReader reader = new InputStreamReader(recipesFile); 175 return CsvTable.importCsv(RecipesTableSchema.SCHEMA, reader); 176 } 177 } 178