• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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