From 9d640d946b28427ffcdeb5dfa0daec3c632e6b8d Mon Sep 17 00:00:00 2001 From: Miruna Barbu Date: Mon, 27 Jul 2015 13:48:55 +0200 Subject: [PATCH] Refactoring to read from multiple file metadata source. --- .../i18n/phonenumbers/MetadataSource.java | 38 +++++ .../MultiFileMetadataSourceImpl.java | 155 ++++++++++++++++++ .../i18n/phonenumbers/PhoneNumberUtil.java | 140 ++++------------ .../MultiFileMetadataSourceImplTest.java | 50 ++++++ .../phonenumbers/PhoneNumberUtilTest.java | 20 --- .../phonenumbers/TestMetadataTestCase.java | 5 +- 6 files changed, 277 insertions(+), 131 deletions(-) create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataSource.java create mode 100644 java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java create mode 100644 java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataSource.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataSource.java new file mode 100644 index 000000000..bedd570f3 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataSource.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.i18n.phonenumbers; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; + +/** + * A source for phone metadata from resources. + */ +interface MetadataSource { + /** + * Gets phone metadata for a region. + * @param regionCode the region code. + * @return the phone metadata for that region, or null if there is none. + */ + PhoneMetadata getMetadataForRegion(String regionCode); + + /** + * Gets phone metadata for a non-geographical region. + * @param countryCallingCode the country calling code. + * @return the phone metadata for that region, or null if there is none. + */ + PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode); +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java new file mode 100644 index 000000000..7c5bb8db9 --- /dev/null +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.i18n.phonenumbers; + +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; +import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Implementation of {@link MetadataSource} that reads from multiple resource files. + */ +final class MultiFileMetadataSourceImpl implements MetadataSource { + + private static final Logger logger = + Logger.getLogger(MultiFileMetadataSourceImpl.class.getName()); + + private static final String META_DATA_FILE_PREFIX = + "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto"; + + // A mapping from a region code to the PhoneMetadata for that region. + // Note: Synchronization, though only needed for the Android version of the library, is used in + // all versions for consistency. + private final Map regionToMetadataMap = + Collections.synchronizedMap(new HashMap()); + + // A mapping from a country calling code for a non-geographical entity to the PhoneMetadata for + // that country calling code. Examples of the country calling codes include 800 (International + // Toll Free Service) and 808 (International Shared Cost Service). + // Note: Synchronization, though only needed for the Android version of the library, is used in + // all versions for consistency. + private final Map countryCodeToNonGeographicalMetadataMap = + Collections.synchronizedMap(new HashMap()); + + // The prefix of the metadata files from which region data is loaded. + private final String currentFilePrefix; + + // The metadata loader used to inject alternative metadata sources. + private final MetadataLoader metadataLoader; + + // It is assumed that metadataLoader is not null. + public MultiFileMetadataSourceImpl(String currentFilePrefix, MetadataLoader metadataLoader) { + this.currentFilePrefix = currentFilePrefix; + this.metadataLoader = metadataLoader; + } + + // It is assumed that metadataLoader is not null. + public MultiFileMetadataSourceImpl(MetadataLoader metadataLoader) { + this(META_DATA_FILE_PREFIX, metadataLoader); + } + + @Override + public PhoneMetadata getMetadataForRegion(String regionCode) { + synchronized (regionToMetadataMap) { + if (!regionToMetadataMap.containsKey(regionCode)) { + // The regionCode here will be valid and won't be '001', so we don't need to worry about + // what to pass in for the country calling code. + loadMetadataFromFile(currentFilePrefix, regionCode, 0, metadataLoader); + } + } + return regionToMetadataMap.get(regionCode); + } + + @Override + public PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) { + synchronized (countryCodeToNonGeographicalMetadataMap) { + if (!countryCodeToNonGeographicalMetadataMap.containsKey(countryCallingCode)) { + loadMetadataFromFile(currentFilePrefix, PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY, + countryCallingCode, metadataLoader); + } + } + return countryCodeToNonGeographicalMetadataMap.get(countryCallingCode); + } + + // @VisibleForTesting + void loadMetadataFromFile(String filePrefix, String regionCode, int countryCallingCode, + MetadataLoader metadataLoader) { + boolean isNonGeoRegion = PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode); + String fileName = filePrefix + "_" + + (isNonGeoRegion ? String.valueOf(countryCallingCode) : regionCode); + InputStream source = metadataLoader.loadMetadata(fileName); + if (source == null) { + logger.log(Level.SEVERE, "missing metadata: " + fileName); + throw new IllegalStateException("missing metadata: " + fileName); + } + ObjectInputStream in = null; + try { + in = new ObjectInputStream(source); + PhoneMetadataCollection metadataCollection = loadMetadataAndCloseInput(in); + List metadataList = metadataCollection.getMetadataList(); + if (metadataList.isEmpty()) { + logger.log(Level.SEVERE, "empty metadata: " + fileName); + throw new IllegalStateException("empty metadata: " + fileName); + } + if (metadataList.size() > 1) { + logger.log(Level.WARNING, "invalid metadata (too many entries): " + fileName); + } + PhoneMetadata metadata = metadataList.get(0); + if (isNonGeoRegion) { + countryCodeToNonGeographicalMetadataMap.put(countryCallingCode, metadata); + } else { + regionToMetadataMap.put(regionCode, metadata); + } + } catch (IOException e) { + logger.log(Level.SEVERE, "cannot load/parse metadata: " + fileName, e); + throw new RuntimeException("cannot load/parse metadata: " + fileName, e); + } + } + + /** + * Loads the metadata protocol buffer from the given stream and closes the stream afterwards. Any + * exceptions that occur while reading the stream are propagated (though exceptions that occur + * when the stream is closed will be ignored). + * + * @param source the non-null stream from which metadata is to be read. + * @return the loaded metadata protocol buffer. + */ + private static PhoneMetadataCollection loadMetadataAndCloseInput(ObjectInputStream source) { + PhoneMetadataCollection metadataCollection = new PhoneMetadataCollection(); + try { + metadataCollection.readExternal(source); + } catch (IOException e) { + logger.log(Level.WARNING, "error reading input (ignored)", e); + } finally { + try { + source.close(); + } catch (IOException e) { + logger.log(Level.WARNING, "error closing input stream (ignored)", e); + } + } + return metadataCollection; + } +} diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java index 391540e9b..76ca61cfb 100644 --- a/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java +++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java @@ -18,15 +18,11 @@ package com.google.i18n.phonenumbers; import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat; import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; -import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection; import com.google.i18n.phonenumbers.Phonemetadata.PhoneNumberDesc; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource; -import java.io.IOException; import java.io.InputStream; -import java.io.ObjectInput; -import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -78,9 +74,6 @@ public class PhoneNumberUtil { // input from overflowing the regular-expression engine. private static final int MAX_INPUT_STRING_LENGTH = 250; - private static final String META_DATA_FILE_PREFIX = - "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProto"; - // Region-code for the unknown region. private static final String UNKNOWN_REGION = "ZZ"; @@ -535,6 +528,9 @@ public class PhoneNumberUtil { abstract boolean verify(PhoneNumber number, String candidate, PhoneNumberUtil util); } + // A source of metadata for different regions. + private final MetadataSource metadataSource; + // A mapping from a country calling code to the region codes which denote the region represented // by that country calling code. In the case of multiple regions sharing a calling code, such as // the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be @@ -546,20 +542,6 @@ public class PhoneNumberUtil { // We set the initial capacity of the HashSet to 35 to offer a load factor of roughly 0.75. private final Set nanpaRegions = new HashSet(35); - // A mapping from a region code to the PhoneMetadata for that region. - // Note: Synchronization, though only needed for the Android version of the library, is used in - // all versions for consistency. - private final Map regionToMetadataMap = - Collections.synchronizedMap(new HashMap()); - - // A mapping from a country calling code for a non-geographical entity to the PhoneMetadata for - // that country calling code. Examples of the country calling codes include 800 (International - // Toll Free Service) and 808 (International Shared Cost Service). - // Note: Synchronization, though only needed for the Android version of the library, is used in - // all versions for consistency. - private final Map countryCodeToNonGeographicalMetadataMap = - Collections.synchronizedMap(new HashMap()); - // A cache for frequently used region-specific regular expressions. // The initial capacity is set to 100 as this seems to be an optimal value for Android, based on // performance measurements. @@ -574,19 +556,13 @@ public class PhoneNumberUtil { // currently contains < 12 elements so the default capacity of 16 (load factor=0.75) is fine. private final Set countryCodesForNonGeographicalRegion = new HashSet(); - // The prefix of the metadata files from which region data is loaded. - private final String currentFilePrefix; - // The metadata loader used to inject alternative metadata sources. - private final MetadataLoader metadataLoader; - /** * This class implements a singleton, the constructor is only visible to facilitate testing. */ // @VisibleForTesting - PhoneNumberUtil(String filePrefix, MetadataLoader metadataLoader, + PhoneNumberUtil(MetadataSource metadataSource, Map> countryCallingCodeToRegionCodeMap) { - this.currentFilePrefix = filePrefix; - this.metadataLoader = metadataLoader; + this.metadataSource = metadataSource; this.countryCallingCodeToRegionCodeMap = countryCallingCodeToRegionCodeMap; for (Map.Entry> entry : countryCallingCodeToRegionCodeMap.entrySet()) { List regionCodes = entry.getValue(); @@ -610,65 +586,6 @@ public class PhoneNumberUtil { nanpaRegions.addAll(countryCallingCodeToRegionCodeMap.get(NANPA_COUNTRY_CODE)); } - // @VisibleForTesting - void loadMetadataFromFile(String filePrefix, String regionCode, int countryCallingCode, - MetadataLoader metadataLoader) { - boolean isNonGeoRegion = REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode); - String fileName = filePrefix + "_" + - (isNonGeoRegion ? String.valueOf(countryCallingCode) : regionCode); - InputStream source = metadataLoader.loadMetadata(fileName); - if (source == null) { - logger.log(Level.SEVERE, "missing metadata: " + fileName); - throw new IllegalStateException("missing metadata: " + fileName); - } - ObjectInputStream in = null; - try { - in = new ObjectInputStream(source); - PhoneMetadataCollection metadataCollection = loadMetadataAndCloseInput(in); - List metadataList = metadataCollection.getMetadataList(); - if (metadataList.isEmpty()) { - logger.log(Level.SEVERE, "empty metadata: " + fileName); - throw new IllegalStateException("empty metadata: " + fileName); - } - if (metadataList.size() > 1) { - logger.log(Level.WARNING, "invalid metadata (too many entries): " + fileName); - } - PhoneMetadata metadata = metadataList.get(0); - if (isNonGeoRegion) { - countryCodeToNonGeographicalMetadataMap.put(countryCallingCode, metadata); - } else { - regionToMetadataMap.put(regionCode, metadata); - } - } catch (IOException e) { - logger.log(Level.SEVERE, "cannot load/parse metadata: " + fileName, e); - throw new RuntimeException("cannot load/parse metadata: " + fileName, e); - } - } - - /** - * Loads the metadata protocol buffer from the given stream and closes the stream afterwards. Any - * exceptions that occur while reading the stream are propagated (though exceptions that occur - * when the stream is closed will be ignored). - * - * @param source the non-null stream from which metadata is to be read. - * @return the loaded metadata protocol buffer. - */ - private static PhoneMetadataCollection loadMetadataAndCloseInput(ObjectInputStream source) { - PhoneMetadataCollection metadataCollection = new PhoneMetadataCollection(); - try { - metadataCollection.readExternal(source); - } catch (IOException e) { - logger.log(Level.WARNING, "error reading input (ignored)", e); - } finally { - try { - source.close(); - } catch (IOException e) { - logger.log(Level.WARNING, "error closing input stream (ignored)", e); - } - } - return metadataCollection; - } - /** * Attempts to extract a possible number from the string passed in. This currently strips all * leading characters that cannot be used to start a phone number. Characters that can be used to @@ -1019,6 +936,26 @@ public class PhoneNumberUtil { return instance; } + /** + * Create a new {@link PhoneNumberUtil} instance to carry out international phone number + * formatting, parsing, or validation. The instance is loaded with all metadata by + * using the metadataSource specified. + * + * This method should only be used in the rare case in which you want to manage your own + * metadata loading. Calling this method multiple times is very expensive, as each time + * a new instance is created from scratch. When in doubt, use {@link #getInstance}. + * + * @param metadataSource Customized metadata source. This should not be null. + * @return a PhoneNumberUtil instance + */ + public static PhoneNumberUtil createInstance(MetadataSource metadataSource) { + if (metadataSource == null) { + throw new IllegalArgumentException("metadataSource could not be null."); + } + return new PhoneNumberUtil(metadataSource, + CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap()); + } + /** * Create a new {@link PhoneNumberUtil} instance to carry out international phone number * formatting, parsing, or validation. The instance is loaded with all metadata by @@ -1028,16 +965,14 @@ public class PhoneNumberUtil { * metadata loading. Calling this method multiple times is very expensive, as each time * a new instance is created from scratch. When in doubt, use {@link #getInstance}. * - * @param metadataLoader Customized metadata loader. If null, default metadata loader will - * be used. This should not be null. + * @param metadataLoader Customized metadata loader. This should not be null. * @return a PhoneNumberUtil instance */ public static PhoneNumberUtil createInstance(MetadataLoader metadataLoader) { if (metadataLoader == null) { throw new IllegalArgumentException("metadataLoader could not be null."); } - return new PhoneNumberUtil(META_DATA_FILE_PREFIX, metadataLoader, - CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap()); + return createInstance(new MultiFileMetadataSourceImpl(metadataLoader)); } /** @@ -2038,27 +1973,14 @@ public class PhoneNumberUtil { if (!isValidRegionCode(regionCode)) { return null; } - synchronized (regionToMetadataMap) { - if (!regionToMetadataMap.containsKey(regionCode)) { - // The regionCode here will be valid and won't be '001', so we don't need to worry about - // what to pass in for the country calling code. - loadMetadataFromFile(currentFilePrefix, regionCode, 0, metadataLoader); - } - } - return regionToMetadataMap.get(regionCode); + return metadataSource.getMetadataForRegion(regionCode); } PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) { - synchronized (countryCodeToNonGeographicalMetadataMap) { - if (!countryCallingCodeToRegionCodeMap.containsKey(countryCallingCode)) { - return null; - } - if (!countryCodeToNonGeographicalMetadataMap.containsKey(countryCallingCode)) { - loadMetadataFromFile( - currentFilePrefix, REGION_CODE_FOR_NON_GEO_ENTITY, countryCallingCode, metadataLoader); - } + if (!countryCallingCodeToRegionCodeMap.containsKey(countryCallingCode)) { + return null; } - return countryCodeToNonGeographicalMetadataMap.get(countryCallingCode); + return metadataSource.getMetadataForNonGeographicalRegion(countryCallingCode); } boolean isNumberPossibleForDesc(String nationalNumber, PhoneNumberDesc numberDesc) { diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java new file mode 100644 index 000000000..6a3f16b24 --- /dev/null +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 The Libphonenumber Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.i18n.phonenumbers; + +/** + * Unit tests for MultiFileMetadataSourceImpl.java. + */ +public class MultiFileMetadataSourceImplTest extends TestMetadataTestCase { + + private final MultiFileMetadataSourceImpl multiFileMetadataSource; + + public MultiFileMetadataSourceImplTest() { + multiFileMetadataSource = new MultiFileMetadataSourceImpl(TEST_META_DATA_FILE_PREFIX, + PhoneNumberUtil.DEFAULT_METADATA_LOADER); + } + + public void testMissingMetadataFileThrowsRuntimeException() { + // In normal usage we should never get a state where we are asking to load metadata that doesn't + // exist. However if the library is packaged incorrectly in the jar, this could happen and the + // best we can do is make sure the exception has the file name in it. + try { + multiFileMetadataSource.loadMetadataFromFile( + "no/such/file", "XX", -1, PhoneNumberUtil.DEFAULT_METADATA_LOADER); + fail("expected exception"); + } catch (RuntimeException e) { + assertTrue("Unexpected error: " + e, e.toString().contains("no/such/file_XX")); + } + try { + multiFileMetadataSource.loadMetadataFromFile("no/such/file", PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY, + 123, PhoneNumberUtil.DEFAULT_METADATA_LOADER); + fail("expected exception"); + } catch (RuntimeException e) { + assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file_123")); + } + } +} diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java index 1f98c0362..725a5904c 100644 --- a/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java @@ -132,26 +132,6 @@ public class PhoneNumberUtilTest extends TestMetadataTestCase { assertNull(phoneUtil.getMetadataForNonGeographicalRegion(-1)); } - public void testMissingMetadataFileThrowsRuntimeException() { - // In normal usage we should never get a state where we are asking to load metadata that doesn't - // exist. However if the library is packaged incorrectly in the jar, this could happen and the - // best we can do is make sure the exception has the file name in it. - try { - phoneUtil.loadMetadataFromFile( - "no/such/file", "XX", -1, PhoneNumberUtil.DEFAULT_METADATA_LOADER); - fail("expected exception"); - } catch (RuntimeException e) { - assertTrue("Unexpected error: " + e, e.toString().contains("no/such/file_XX")); - } - try { - phoneUtil.loadMetadataFromFile("no/such/file", PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY, - 123, PhoneNumberUtil.DEFAULT_METADATA_LOADER); - fail("expected exception"); - } catch (RuntimeException e) { - assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file_123")); - } - } - public void testGetInstanceLoadUSMetadata() { PhoneMetadata metadata = phoneUtil.getMetadataForRegion(RegionCode.US); assertEquals("US", metadata.getId()); diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/TestMetadataTestCase.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/TestMetadataTestCase.java index 8845eb0c6..99def5ae2 100644 --- a/java/libphonenumber/test/com/google/i18n/phonenumbers/TestMetadataTestCase.java +++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/TestMetadataTestCase.java @@ -27,7 +27,7 @@ import junit.framework.TestCase; * @author Shaopeng Jia */ public class TestMetadataTestCase extends TestCase { - private static final String TEST_META_DATA_FILE_PREFIX = + protected static final String TEST_META_DATA_FILE_PREFIX = "/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting"; protected final PhoneNumberUtil phoneUtil; @@ -38,7 +38,8 @@ public class TestMetadataTestCase extends TestCase { static PhoneNumberUtil initializePhoneUtilForTesting() { PhoneNumberUtil phoneUtil = new PhoneNumberUtil( - TEST_META_DATA_FILE_PREFIX, PhoneNumberUtil.DEFAULT_METADATA_LOADER, + new MultiFileMetadataSourceImpl(TEST_META_DATA_FILE_PREFIX, + PhoneNumberUtil.DEFAULT_METADATA_LOADER), CountryCodeToRegionCodeMapForTesting.getCountryCodeToRegionCodeMap()); PhoneNumberUtil.setInstance(phoneUtil); return phoneUtil;