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.collect.ImmutableMap; 19 import com.google.common.collect.Multimap; 20 import com.google.i18n.phonenumbers.metadata.table.Column; 21 import com.google.phonenumbers.migrator.MigrationJob.MigrationReport; 22 import java.io.IOException; 23 import java.nio.file.Files; 24 import java.nio.file.Path; 25 import java.nio.file.Paths; 26 import java.util.Scanner; 27 import picocli.CommandLine; 28 import picocli.CommandLine.ArgGroup; 29 import picocli.CommandLine.Command; 30 import picocli.CommandLine.Help.Ansi; 31 import picocli.CommandLine.Option; 32 33 @Command(name = "Command Line Migrator Tool:", 34 description = "Please enter a path to a text file containing E.164 phone numbers " 35 + "(e.g. +4434567891, +1234568890) from the same country or a single E.164 number as " 36 + "well as the corresponding BCP-47 country code (e.g. 44, 1) to begin migrations.\n") 37 public final class CommandLineMain { 38 /** 39 * Fields cannot be private or final to allow for @Command annotation to set and retrieve values. 40 */ 41 42 @ArgGroup(multiplicity = "1") 43 NumberInputType numberInput; 44 45 static class NumberInputType { 46 @Option(names = {"-n", "--number"}, 47 description = "Single E.164 phone number to migrate (e.g. \"+1234567890\") or an internationally formatted " 48 + "number starting with '+' (e.g. \"+12 (345) 67-890\") which will have any non-digit characters after " 49 + "the leading '+' removed for processing.") 50 String number; 51 52 @Option(names = {"-f", "--file"}, 53 description = "Text file to be migrated which contains one E.164 phone " 54 + "number per line") 55 String file; 56 } 57 58 @Option(names = {"-c", "--countryCode"}, 59 required = true, 60 description = "The BCP-47 country code the given phone number(s) belong to (e.g. 44)") 61 String countryCode; 62 63 @ArgGroup() 64 OptionalParameterType optionalParameter; 65 66 static class OptionalParameterType { 67 @Option(names = {"-e", "--exportInvalidMigrations"}, 68 description = "boolean flag specifying that text files created after the migration process" 69 + " for standard recipe --file migrations should contain the migrated version of a given" 70 + " phone number, regardless of whether the migration resulted in an invalid phone number." 71 + " By default, a strict approach is used and when a migration is seen as invalid, the" 72 + " original phone number is written to file. Invalid numbers will be printed at the" 73 + " bottom of the text file.") 74 boolean exportInvalidMigrations; 75 76 @Option(names = {"-r", "--customRecipe"}, 77 description = "Csv file containing a custom migration recipes table. When using custom recipes" 78 + ", validity checks on migrated numbers will not be performed. Note: custom recipes must" 79 + " be run with the --exportInvalidMigrations flag.") 80 String customRecipe; 81 } 82 83 @Option(names = {"-h", "--help"}, description = "Display help", usageHelp = true) 84 boolean help; 85 main(String[] args)86 public static void main(String[] args) throws IOException { 87 CommandLineMain clm = CommandLine.populateCommand(new CommandLineMain(), args); 88 if (clm.help) { 89 CommandLine.usage(clm, System.out, Ansi.AUTO); 90 } else { 91 MigrationJob migrationJob; 92 if (clm.numberInput.number != null) { 93 if (clm.optionalParameter != null && clm.optionalParameter.customRecipe != null) { 94 migrationJob = MigrationFactory 95 .createCustomRecipeMigration(clm.numberInput.number, clm.countryCode, 96 MigrationFactory.importRecipes(Files.newInputStream(Paths.get(clm.optionalParameter.customRecipe)))); 97 } else { 98 migrationJob = MigrationFactory.createMigration(clm.numberInput.number, clm.countryCode); 99 } 100 } else { 101 if (clm.optionalParameter != null && clm.optionalParameter.customRecipe != null) { 102 migrationJob = MigrationFactory 103 .createCustomRecipeMigration(Paths.get(clm.numberInput.file), clm.countryCode, 104 MigrationFactory.importRecipes(Files.newInputStream(Paths.get(clm.optionalParameter.customRecipe)))); 105 } else { 106 migrationJob = MigrationFactory 107 .createMigration(Paths.get(clm.numberInput.file), clm.countryCode, 108 clm.optionalParameter != null && clm.optionalParameter.exportInvalidMigrations); 109 } 110 } 111 112 MigrationReport mr = migrationJob.getMigrationReportForCountry(); 113 System.out.println("Migration of country code +" + migrationJob.getCountryCode() + " phone " 114 + "number(s):"); 115 if (clm.numberInput.file != null) { 116 printFileReport(mr, Paths.get(clm.numberInput.file)); 117 } else { 118 printNumberReport(mr); 119 } 120 } 121 } 122 123 /** Details printed to console after a --file type migration. */ printFileReport(MigrationReport mr, Path originalFile)124 private static void printFileReport(MigrationReport mr, Path originalFile) throws IOException { 125 String newFile = mr.exportToFile(originalFile.getFileName().toString()); 126 Scanner scanner = new Scanner((System.in)); 127 String response = ""; 128 129 System.out.println("New numbers file created at: " + System.getProperty("user.dir") + "/" + newFile); 130 while (!response.equals("0")) { 131 System.out.println("\n(0) Exit"); 132 System.out.println("(1) Print Metrics"); 133 System.out.println("(2) View All Recipes Used"); 134 System.out.print("Select from the above options: "); 135 response = scanner.nextLine(); 136 if (response.equals("1")) { 137 mr.printMetrics(); 138 } else if (response.equals("2")) { 139 printUsedRecipes(mr); 140 } 141 } 142 } 143 144 /** Details printed to console after a --number type migration. */ printNumberReport(MigrationReport mr)145 private static void printNumberReport(MigrationReport mr) { 146 if (mr.getValidMigrations().size() == 1) { 147 System.out.println("Successful migration into: +" 148 + mr.getValidMigrations().get(0).getMigratedNumber()); 149 printUsedRecipes(mr); 150 } else if (mr.getInvalidMigrations().size() == 1) { 151 System.out.println("The number was migrated into '+" 152 + mr.getInvalidMigrations().get(0).getMigratedNumber() + "' but this number was not " 153 + "seen as being valid and dialable when inspecting our data for the given country"); 154 printUsedRecipes(mr); 155 } else if (mr.getValidUntouchedEntries().size() == 1) { 156 System.out.println("This number was seen to already be valid and dialable based on " 157 + "our data for the given country"); 158 } else { 159 System.out.println("This number could not be migrated using any of the recipes from the given" 160 + " recipes file"); 161 } 162 } 163 printUsedRecipes(MigrationReport mr)164 private static void printUsedRecipes(MigrationReport mr) { 165 Multimap<ImmutableMap<Column<?>, Object>, MigrationResult> recipeToNumbers = mr.getAllRecipesUsed(); 166 System.out.println("\nRecipe(s) Used:"); 167 for (ImmutableMap<Column<?>, Object> recipe : recipeToNumbers.keySet()) { 168 System.out.println("* " + RecipesTableSchema.formatRecipe(recipe)); 169 recipeToNumbers.get(recipe).forEach(result -> System.out.println("\t" + result)); 170 System.out.println(""); 171 } 172 } 173 } 174