Browse Source

Added new classes with behavior from MetadataManager & MetadataSource

bumping-java-to-version-7
tijanavg 4 years ago
parent
commit
35c4932ee8
30 changed files with 1564 additions and 4 deletions
  1. +1
    -1
      java/libphonenumber/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java
  2. +56
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/internal/GeoEntityUtility.java
  3. +92
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/DefaultMetadataDependenciesProvider.java
  4. +32
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/ClassPathResourceMetadataLoader.java
  5. +108
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/MetadataParser.java
  6. +78
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuard.java
  7. +69
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainer.java
  8. +37
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSource.java
  9. +57
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSourceImpl.java
  10. +74
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainer.java
  11. +35
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataBootstrappingGuard.java
  12. +32
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataContainer.java
  13. +21
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSource.java
  14. +72
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSourceImpl.java
  15. +42
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProvider.java
  16. +47
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/NonGeographicalEntityMetadataSource.java
  17. +36
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/PhoneMetadataFileNameProvider.java
  18. +40
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSource.java
  19. +62
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSourceImpl.java
  20. +35
    -0
      java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProvider.java
  21. +53
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/internal/GeoEntityUtilityTest.java
  22. +21
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/PhoneMetadataCollectionUtil.java
  23. +98
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/init/MetadataParserTest.java
  24. +117
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuardTest.java
  25. +78
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainerTest.java
  26. +74
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainerTest.java
  27. +51
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProviderTest.java
  28. +37
    -0
      java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProviderTest.java
  29. +6
    -0
      java/pom.xml
  30. +3
    -3
      tools/java/java-build/src/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java

+ 1
- 1
java/libphonenumber/src/com/google/i18n/phonenumbers/CountryCodeToRegionCodeMap.java View File

@ -30,7 +30,7 @@ public class CountryCodeToRegionCodeMap {
// country/region represented by that country code. In the case of multiple
// countries sharing a calling code, such as the NANPA countries, the one
// indicated with "isMainCountryForCode" in the metadata should be first.
static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {
public static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {
// The capacity is set to 286 as there are 215 different entries,
// and this offers a load factor of roughly 0.75.
Map<Integer, List<String>> countryCodeToRegionCodeMap =


+ 56
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/internal/GeoEntityUtility.java View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2022 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.internal;
import com.google.i18n.phonenumbers.CountryCodeToRegionCodeMap;
import java.util.List;
/**
* Utility class for checking whether identifiers region code and country calling code belong
* to geographical entities. For more information about geo vs. non-geo entities see {@link
* com.google.i18n.phonenumbers.metadata.source.RegionMetadataSource} and {@link
* com.google.i18n.phonenumbers.metadata.source.NonGeographicalEntityMetadataSource}
*/
public final class GeoEntityUtility {
/** Region code with a special meaning, used to mark non-geographical entities */
public static final String REGION_CODE_FOR_NON_GEO_ENTITIES = "001";
/** Determines whether {@code regionCode} belongs to a geographical entity. */
public static boolean isGeoEntity(String regionCode) {
return !regionCode.equals(REGION_CODE_FOR_NON_GEO_ENTITIES);
}
/**
* Determines whether {@code countryCallingCode} belongs to a geographical entity.
*
* <p>A single country calling code could map to several different regions. It is considered that
* {@code countryCallingCode} belongs to a geo entity if all of these regions are geo entities
*
* <p>Note that this method will not throw an exception even when the underlying mapping for the
* {@code countryCallingCode} does not exist, instead it will return {@code false}
*/
public static boolean isGeoEntity(int countryCallingCode) {
List<String> regionCodesForCountryCallingCode =
CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap().get(countryCallingCode);
return regionCodesForCountryCallingCode != null
&& !regionCodesForCountryCallingCode.contains(REGION_CODE_FOR_NON_GEO_ENTITIES);
}
private GeoEntityUtility() {}
}

+ 92
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/DefaultMetadataDependenciesProvider.java View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2022 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.metadata;
import com.google.i18n.phonenumbers.MetadataLoader;
import com.google.i18n.phonenumbers.metadata.init.ClassPathResourceMetadataLoader;
import com.google.i18n.phonenumbers.metadata.init.MetadataParser;
import com.google.i18n.phonenumbers.metadata.source.FormattingMetadataSource;
import com.google.i18n.phonenumbers.metadata.source.FormattingMetadataSourceImpl;
import com.google.i18n.phonenumbers.metadata.source.MetadataSource;
import com.google.i18n.phonenumbers.metadata.source.MetadataSourceImpl;
import com.google.i18n.phonenumbers.metadata.source.RegionMetadataSource;
import com.google.i18n.phonenumbers.metadata.source.RegionMetadataSourceImpl;
import com.google.i18n.phonenumbers.metadata.source.SingleFileModeFileNameProvider;
/**
* Provides metadata init & source dependencies when metadata is stored in multi-file mode and
* loaded as a classpath resource.
*/
public final class DefaultMetadataDependenciesProvider {
private static final DefaultMetadataDependenciesProvider INSTANCE = new DefaultMetadataDependenciesProvider();
public static DefaultMetadataDependenciesProvider getInstance() {
return INSTANCE;
}
private DefaultMetadataDependenciesProvider() {}
private final MetadataParser metadataParser = MetadataParser.newStrictParser();
private final MetadataLoader metadataLoader = new ClassPathResourceMetadataLoader();
private final MetadataSource phoneNumberMetadataSource =
new MetadataSourceImpl(
new SingleFileModeFileNameProvider(
"/com/google/i18n/phonenumbers/buildtools/PhoneNumberMetadataProto"),
metadataLoader,
metadataParser);
private final RegionMetadataSource shortNumberMetadataSource =
new RegionMetadataSourceImpl(
new SingleFileModeFileNameProvider(
"/com/google/i18n/phonenumbers/buildtools/ShortNumberMetadataProto"),
metadataLoader,
metadataParser);
private final FormattingMetadataSource alternateFormatsMetadataSource =
new FormattingMetadataSourceImpl(
new SingleFileModeFileNameProvider(
"/com/google/i18n/phonenumbers/buildtools/PhoneNumberAlternateFormatsProto"),
metadataLoader,
metadataParser);
public MetadataParser getMetadataParser() {
return metadataParser;
}
public MetadataLoader getMetadataLoader() {
return metadataLoader;
}
public MetadataSource getPhoneNumberMetadataSource() {
return phoneNumberMetadataSource;
}
public RegionMetadataSource getShortNumberMetadataSource() {
return shortNumberMetadataSource;
}
public FormattingMetadataSource getAlternateFormatsMetadataSource() {
return alternateFormatsMetadataSource;
}
public String getCarrierDataDirectory() {
return "/com/google/i18n/phonenumbers/buildtools/carrier_data/";
}
public String getGeocodingDataDirectory() {
return "/com/google/i18n/phonenumbers/buildtools/geocoding_data/";
}
}

+ 32
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/ClassPathResourceMetadataLoader.java View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2022 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.metadata.init;
import com.google.i18n.phonenumbers.MetadataLoader;
import java.io.InputStream;
/**
* A {@link MetadataLoader} implementation that reads phone number metadata files as classpath
* resources.
*/
public final class ClassPathResourceMetadataLoader implements MetadataLoader {
@Override
public InputStream loadMetadata(String metadataFileName) {
return ClassPathResourceMetadataLoader.class.getResourceAsStream(metadataFileName);
}
}

+ 108
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/init/MetadataParser.java View File

@ -0,0 +1,108 @@
/*
* Copyright (C) 2022 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.metadata.init;
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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Exposes single method for parsing {@link InputStream} content into {@link Collection} of {@link
* PhoneMetadata}
*/
public final class MetadataParser {
private static final Logger logger = Logger.getLogger(MetadataParser.class.getName());
/**
* Creates new instance in lenient mode, see {@link MetadataParser#parse(InputStream)} for more
* info.
*/
public static MetadataParser newLenientParser() {
return new MetadataParser(false);
}
/**
* Creates new instance in strict mode, see {@link MetadataParser#parse(InputStream)} for more
* info
*/
public static MetadataParser newStrictParser() {
return new MetadataParser(true);
}
private final boolean strictMode;
private MetadataParser(boolean strictMode) {
this.strictMode = strictMode;
}
/**
* Parses given {@link InputStream} into a {@link Collection} of {@link PhoneMetadata}.
*
* @throws IllegalArgumentException if {@code source} is {@code null} and strict mode is on
* @return parsed {@link PhoneMetadata}, or empty {@link Collection} if {@code source} is {@code
* null} and lenient mode is on
*/
public Collection<PhoneMetadata> parse(InputStream source) {
if (source == null) {
return handleNullSource();
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(source);
PhoneMetadataCollection phoneMetadataCollection = new PhoneMetadataCollection();
phoneMetadataCollection.readExternal(ois);
List<PhoneMetadata> phoneMetadata = phoneMetadataCollection.getMetadataList();
// Sanity check; this should not happen if provided InputStream is valid
if (phoneMetadata.isEmpty()) {
throw new IllegalStateException("Empty metadata");
}
return phoneMetadataCollection.getMetadataList();
} catch (IOException e) {
throw new IllegalStateException("Unable to parse metadata file", e);
} finally {
if (ois != null) {
// This will close all underlying streams as well, including source.
close(ois);
} else {
close(source);
}
}
}
private List<PhoneMetadata> handleNullSource() {
if (strictMode) {
throw new IllegalArgumentException("Source cannot be null");
}
return Collections.emptyList();
}
private void close(InputStream inputStream) {
try {
inputStream.close();
} catch (IOException e) {
logger.log(Level.WARNING, "Error closing input stream (ignored)", e);
}
}
}

+ 78
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuard.java View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.MetadataLoader;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.metadata.init.MetadataParser;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* A blocking implementation of {@link MetadataBootstrappingGuard}. Can be used for both single-file
* (bulk) and multi-file metadata
*
* @param <T> needs to extend {@link MetadataContainer}
*/
final class BlockingMetadataBootstrappingGuard<T extends MetadataContainer>
implements MetadataBootstrappingGuard<T> {
private final MetadataLoader metadataLoader;
private final MetadataParser metadataParser;
private final T metadataContainer;
private final Set<String> loadedFiles;
BlockingMetadataBootstrappingGuard(
MetadataLoader metadataLoader, MetadataParser metadataParser, T metadataContainer) {
this.metadataLoader = metadataLoader;
this.metadataParser = metadataParser;
this.metadataContainer = metadataContainer;
this.loadedFiles = new HashSet<>();
}
@Override
public T getOrBootstrap(String phoneMetadataFile) {
if (!loadedFiles.contains(phoneMetadataFile)) {
bootstrapMetadata(phoneMetadataFile);
}
return metadataContainer;
}
private synchronized void bootstrapMetadata(String phoneMetadataFile) {
// Additional check is needed because multiple threads could pass the first check when calling
// getOrBootstrap() at the same time for unloaded metadata file
if (loadedFiles.contains(phoneMetadataFile)) {
return;
}
Collection<PhoneMetadata> phoneMetadata = read(phoneMetadataFile);
for (PhoneMetadata metadata : phoneMetadata) {
metadataContainer.accept(metadata);
}
loadedFiles.add(phoneMetadataFile);
}
private Collection<PhoneMetadata> read(String phoneMetadataFile) {
try {
InputStream metadataStream = metadataLoader.loadMetadata(phoneMetadataFile);
return metadataParser.parse(metadataStream);
} catch (IllegalArgumentException | IllegalStateException e) {
throw new IllegalStateException("Failed to read file " + phoneMetadataFile, e);
}
}
}

+ 69
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainer.java View File

@ -0,0 +1,69 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.internal.GeoEntityUtility;
/**
* Implementation of {@link MetadataContainer} which is a composition of different {@link
* MapBackedMetadataContainer}s. It adds items to a single simpler container at a time depending on
* the content of {@link PhoneMetadata}.
*/
final class CompositeMetadataContainer implements MetadataContainer {
private final MapBackedMetadataContainer<Integer> metadataByCountryCode =
MapBackedMetadataContainer.byCountryCallingCode();
private final MapBackedMetadataContainer<String> metadataByRegionCode =
MapBackedMetadataContainer.byRegionCode();
/**
* Intended to be called for geographical regions only. For non-geographical entities, use {@link
* CompositeMetadataContainer#getMetadataBy(int)}
*/
PhoneMetadata getMetadataBy(String regionCode) {
return metadataByRegionCode.getMetadataBy(regionCode);
}
/**
* Intended to be called for non-geographical entities only, such as 800 (country code assigned to
* the Universal International Freephone Service). For geographical regions, use {@link
* CompositeMetadataContainer#getMetadataBy(String)}
*/
PhoneMetadata getMetadataBy(int countryCallingCode) {
return metadataByCountryCode.getMetadataBy(countryCallingCode);
}
/**
* If the metadata belongs to a specific geographical region (it has a region code other than
* {@link GeoEntityUtility#REGION_CODE_FOR_NON_GEO_ENTITIES}), it will be added to a {@link
* MapBackedMetadataContainer} which stores metadata by region code. Otherwise, it will be added
* to a {@link MapBackedMetadataContainer} which stores metadata by country calling code. This
* means that {@link CompositeMetadataContainer#getMetadataBy(int)} will not work for country
* calling codes such as 41 (country calling code for Switzerland), only for country calling codes
* such as 800 (country code assigned to the Universal International Freephone Service)
*/
@Override
public void accept(PhoneMetadata phoneMetadata) {
String regionCode = metadataByRegionCode.getKeyProvider().getKeyOf(phoneMetadata);
if (GeoEntityUtility.isGeoEntity(regionCode)) {
metadataByRegionCode.accept(phoneMetadata);
} else {
metadataByCountryCode.accept(phoneMetadata);
}
}
}

+ 37
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSource.java View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
/** A source of formatting phone metadata. */
public interface FormattingMetadataSource {
/**
* Returns formatting phone metadata for provided country calling code.
*
* <p>This method is similar to the one in {@link
* NonGeographicalEntityMetadataSource#getMetadataForNonGeographicalRegion(int)}, except that it
* will not fail for geographical regions, it can be used for both geo- and non-geo entities.
*
* <p>In case the provided {@code countryCallingCode} maps to several different regions, only one
* would contain formatting metadata.
*
* @return the phone metadata for provided {@code countryCallingCode}, or null if there is none.
*/
PhoneMetadata getFormattingMetadataForCountryCallingCode(int countryCallingCode);
}

+ 57
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/FormattingMetadataSourceImpl.java View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.MetadataLoader;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.metadata.init.MetadataParser;
/**
* Implementation of {@link FormattingMetadataSource} guarded by {@link MetadataBootstrappingGuard}
*
* <p>By default, a {@link BlockingMetadataBootstrappingGuard} will be used, but any custom
* implementation can be injected.
*/
public final class FormattingMetadataSourceImpl implements FormattingMetadataSource {
private final PhoneMetadataFileNameProvider phoneMetadataFileNameProvider;
private final MetadataBootstrappingGuard<MapBackedMetadataContainer<Integer>> bootstrappingGuard;
public FormattingMetadataSourceImpl(
PhoneMetadataFileNameProvider phoneMetadataFileNameProvider,
MetadataBootstrappingGuard<MapBackedMetadataContainer<Integer>> bootstrappingGuard) {
this.phoneMetadataFileNameProvider = phoneMetadataFileNameProvider;
this.bootstrappingGuard = bootstrappingGuard;
}
public FormattingMetadataSourceImpl(
PhoneMetadataFileNameProvider phoneMetadataFileNameProvider,
MetadataLoader metadataLoader,
MetadataParser metadataParser) {
this(
phoneMetadataFileNameProvider,
new BlockingMetadataBootstrappingGuard<>(
metadataLoader, metadataParser, MapBackedMetadataContainer.byCountryCallingCode()));
}
@Override
public PhoneMetadata getFormattingMetadataForCountryCallingCode(int countryCallingCode) {
return bootstrappingGuard
.getOrBootstrap(phoneMetadataFileNameProvider.getFor(countryCallingCode))
.getMetadataBy(countryCallingCode);
}
}

+ 74
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainer.java View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A {@link MetadataContainer} implementation backed by a {@link ConcurrentHashMap} with generic
* keys.
*/
final class MapBackedMetadataContainer<T> implements MetadataContainer {
static MapBackedMetadataContainer<String> byRegionCode() {
return new MapBackedMetadataContainer<>(
new KeyProvider<String>() {
@Override
public String getKeyOf(PhoneMetadata phoneMetadata) {
return phoneMetadata.getId();
}
});
}
static MapBackedMetadataContainer<Integer> byCountryCallingCode() {
return new MapBackedMetadataContainer<>(
new KeyProvider<Integer>() {
@Override
public Integer getKeyOf(PhoneMetadata phoneMetadata) {
return phoneMetadata.getCountryCode();
}
});
}
private final ConcurrentMap<T, PhoneMetadata> metadataMap;
private final KeyProvider<T> keyProvider;
private MapBackedMetadataContainer(KeyProvider<T> keyProvider) {
this.metadataMap = new ConcurrentHashMap<>();
this.keyProvider = keyProvider;
}
PhoneMetadata getMetadataBy(T key) {
return key != null ? metadataMap.get(key) : null;
}
KeyProvider<T> getKeyProvider() {
return keyProvider;
}
@Override
public void accept(PhoneMetadata phoneMetadata) {
metadataMap.put(keyProvider.getKeyOf(phoneMetadata), phoneMetadata);
}
interface KeyProvider<T> {
T getKeyOf(PhoneMetadata phoneMetadata);
}
}

+ 35
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataBootstrappingGuard.java View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2022 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.metadata.source;
/**
* Guard that ensures that metadata bootstrapping process (loading and parsing) is triggered only
* once per metadata file.
*
* @param <T> needs to extend {@link MetadataContainer}
*/
public interface MetadataBootstrappingGuard<T extends MetadataContainer> {
/**
* If metadata from the provided file has not yet been read, invokes loading and parsing from the
* provided file and adds the result to guarded {@link MetadataContainer}.
*
* @param phoneMetadataFile to read from
* @return guarded {@link MetadataContainer}
*/
T getOrBootstrap(String phoneMetadataFile);
}

+ 32
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataContainer.java View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
/**
* A container for {@link PhoneMetadata}
*/
interface MetadataContainer {
/**
* Adds {@link PhoneMetadata} to the container. It depends on the implementation of the interface
* what this means, for example {@link MapBackedMetadataContainer} simply adds the provided
* metadata into the backing map. Implementing classes should ensure thread-safety.
*/
void accept(PhoneMetadata phoneMetadata);
}

+ 21
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSource.java View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2022 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.metadata.source;
/** A source of phone metadata split by different regions. */
public interface MetadataSource extends RegionMetadataSource, NonGeographicalEntityMetadataSource {
}

+ 72
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MetadataSourceImpl.java View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.MetadataLoader;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.internal.GeoEntityUtility;
import com.google.i18n.phonenumbers.metadata.init.MetadataParser;
/**
* Implementation of {@link MetadataSource} guarded by {@link MetadataBootstrappingGuard}.
*
* <p>By default, a {@link BlockingMetadataBootstrappingGuard} will be used, but any custom
* implementation can be injected.
*/
public final class MetadataSourceImpl implements MetadataSource {
private final PhoneMetadataFileNameProvider phoneMetadataFileNameProvider;
private final MetadataBootstrappingGuard<CompositeMetadataContainer> bootstrappingGuard;
public MetadataSourceImpl(
PhoneMetadataFileNameProvider phoneMetadataFileNameProvider,
MetadataBootstrappingGuard<CompositeMetadataContainer> bootstrappingGuard) {
this.phoneMetadataFileNameProvider = phoneMetadataFileNameProvider;
this.bootstrappingGuard = bootstrappingGuard;
}
public MetadataSourceImpl(
PhoneMetadataFileNameProvider phoneMetadataFileNameProvider,
MetadataLoader metadataLoader,
MetadataParser metadataParser) {
this(
phoneMetadataFileNameProvider,
new BlockingMetadataBootstrappingGuard<>(
metadataLoader, metadataParser, new CompositeMetadataContainer()));
}
@Override
public PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) {
if (GeoEntityUtility.isGeoEntity(countryCallingCode)) {
throw new IllegalArgumentException(
countryCallingCode + " calling code belongs to a geo entity");
}
return bootstrappingGuard
.getOrBootstrap(phoneMetadataFileNameProvider.getFor(countryCallingCode))
.getMetadataBy(countryCallingCode);
}
@Override
public PhoneMetadata getMetadataForRegion(String regionCode) {
if (!GeoEntityUtility.isGeoEntity(regionCode)) {
throw new IllegalArgumentException(regionCode + " region code is a non-geo entity");
}
return bootstrappingGuard
.getOrBootstrap(phoneMetadataFileNameProvider.getFor(regionCode))
.getMetadataBy(regionCode);
}
}

+ 42
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProvider.java View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2022 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.metadata.source;
import java.util.regex.Pattern;
/**
* {@link PhoneMetadataFileNameProvider} implementation which appends key as a suffix to the
* predefined metadata file name base.
*/
public final class MultiFileModeFileNameProvider implements PhoneMetadataFileNameProvider {
private final String phoneMetadataFileNamePrefix;
private static final Pattern ALPHANUMERIC = Pattern.compile("^[\\p{L}\\p{N}]+$");
public MultiFileModeFileNameProvider(String phoneMetadataFileNameBase) {
this.phoneMetadataFileNamePrefix = phoneMetadataFileNameBase + "_";
}
@Override
public String getFor(Object key) {
String keyAsString = key.toString();
if (!ALPHANUMERIC.matcher(keyAsString).matches()) {
throw new IllegalArgumentException("Invalid key: " + keyAsString);
}
return phoneMetadataFileNamePrefix + key;
}
}

+ 47
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/NonGeographicalEntityMetadataSource.java View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
/**
* A source of phone metadata for non-geographical entities.
*
* <p>Non-geographical entities are phone number ranges that have a country calling code, but either
* do not belong to an actual country (some international services), or belong to a region which has
* a different country calling code from the country it is part of. Examples of such ranges are
* those starting with:
*
* <ul>
* <li>800 - country code assigned to the Universal International Freephone Service
* <li>808 - country code assigned to the International Shared Cost Service
* <li>870 - country code assigned to the Pitcairn Islands
* <li>...
* </ul>
*/
public interface NonGeographicalEntityMetadataSource {
/**
* Gets phone metadata for a non-geographical entity.
*
* @param countryCallingCode the country calling code.
* @return the phone metadata for that entity, or null if there is none.
* @throws IllegalArgumentException if provided {@code countryCallingCode} does not belong to a
* non-geographical entity
*/
PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode);
}

+ 36
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/PhoneMetadataFileNameProvider.java View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 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.metadata.source;
/**
* Abstraction responsible for inferring the metadata file name.
*
* <p>Two implementations are available:
*
* <ul>
* <li>{@link SingleFileModeFileNameProvider} for single-file metadata.
* <li>{@link MultiFileModeFileNameProvider} for multi-file metadata.
* </ul>
*/
public interface PhoneMetadataFileNameProvider {
/**
* Returns phone metadata file path for the given key. Assumes that key.toString() is
* well-defined.
*/
String getFor(Object key);
}

+ 40
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSource.java View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.internal.GeoEntityUtility;
/**
* A source of phone metadata split by geographical regions.
*/
public interface RegionMetadataSource {
/**
* Returns phone metadata for provided geographical region.
*
* <p>The {@code regionCode} must be different from {@link
* GeoEntityUtility#REGION_CODE_FOR_NON_GEO_ENTITIES}, which has a special meaning and is used to
* mark non-geographical regions (see {@link NonGeographicalEntityMetadataSource} for more
* information).
*
* @return the phone metadata for provided {@code regionCode}, or null if there is none.
* @throws IllegalArgumentException if provided {@code regionCode} is {@link
* GeoEntityUtility#REGION_CODE_FOR_NON_GEO_ENTITIES}
*/
PhoneMetadata getMetadataForRegion(String regionCode);
}

+ 62
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/RegionMetadataSourceImpl.java View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2022 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.metadata.source;
import com.google.i18n.phonenumbers.MetadataLoader;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.internal.GeoEntityUtility;
import com.google.i18n.phonenumbers.metadata.init.MetadataParser;
/**
* Implementation of {@link RegionMetadataSource} guarded by {@link MetadataBootstrappingGuard}
*
* <p>By default, a {@link BlockingMetadataBootstrappingGuard} will be used, but any custom
* implementation can be injected.
*/
public final class RegionMetadataSourceImpl implements RegionMetadataSource {
private final PhoneMetadataFileNameProvider phoneMetadataFileNameProvider;
private final MetadataBootstrappingGuard<MapBackedMetadataContainer<String>>
bootstrappingGuard;
public RegionMetadataSourceImpl(
PhoneMetadataFileNameProvider phoneMetadataFileNameProvider,
MetadataBootstrappingGuard<MapBackedMetadataContainer<String>> bootstrappingGuard) {
this.phoneMetadataFileNameProvider = phoneMetadataFileNameProvider;
this.bootstrappingGuard = bootstrappingGuard;
}
public RegionMetadataSourceImpl(
PhoneMetadataFileNameProvider phoneMetadataFileNameProvider,
MetadataLoader metadataLoader,
MetadataParser metadataParser) {
this(
phoneMetadataFileNameProvider,
new BlockingMetadataBootstrappingGuard<>(
metadataLoader, metadataParser, MapBackedMetadataContainer.byRegionCode()));
}
@Override
public PhoneMetadata getMetadataForRegion(String regionCode) {
if (!GeoEntityUtility.isGeoEntity(regionCode)) {
throw new IllegalArgumentException(regionCode + " region code is a non-geo entity");
}
return bootstrappingGuard
.getOrBootstrap(phoneMetadataFileNameProvider.getFor(regionCode))
.getMetadataBy(regionCode);
}
}

+ 35
- 0
java/libphonenumber/src/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProvider.java View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2022 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.metadata.source;
/**
* {@link PhoneMetadataFileNameProvider} implementation that returns the same metadata file name for
* each key
*/
public final class SingleFileModeFileNameProvider implements PhoneMetadataFileNameProvider {
private final String phoneMetadataFileName;
public SingleFileModeFileNameProvider(String phoneMetadataFileName) {
this.phoneMetadataFileName = phoneMetadataFileName;
}
@Override
public String getFor(Object key) {
return phoneMetadataFileName;
}
}

+ 53
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/internal/GeoEntityUtilityTest.java View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2022 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.internal;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class GeoEntityUtilityTest {
@Test
public void isGeoEntity_shouldReturnTrueForCountryRegionCode() {
assertTrue(GeoEntityUtility.isGeoEntity("DE"));
}
@Test
public void isGeoEntity_shouldReturnFalseForWorldRegionCode() {
assertFalse(GeoEntityUtility.isGeoEntity("001"));
}
@Test
public void isGeoEntity_shouldReturnTrueForCountryCallingCode() {
assertTrue(GeoEntityUtility.isGeoEntity(41));
}
@Test
public void isGeoEntity_shouldReturnFalseForInternationalSharedCostServiceCallingCode() {
assertFalse(GeoEntityUtility.isGeoEntity(808));
}
@Test
public void isGeoEntity_shouldReturnFalseForNonExistingCountryCallingCode() {
assertFalse(GeoEntityUtility.isGeoEntity(111111111));
}
}

+ 21
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/PhoneMetadataCollectionUtil.java View File

@ -0,0 +1,21 @@
package com.google.i18n.phonenumbers.metadata;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
public class PhoneMetadataCollectionUtil {
public static InputStream toInputStream(PhoneMetadataCollection metadata) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
metadata.writeExternal(objectOutputStream);
objectOutputStream.flush();
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
objectOutputStream.close();
return inputStream;
}
}

+ 98
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/init/MetadataParserTest.java View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2022 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.metadata.init;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection;
import com.google.i18n.phonenumbers.metadata.PhoneMetadataCollectionUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class MetadataParserTest {
private static final MetadataParser metadataParser = MetadataParser.newStrictParser();
@Test
public void parse_shouldThrowExceptionForNullInput() {
assertThrows(
IllegalArgumentException.class,
new ThrowingRunnable() {
@Override
public void run() {
metadataParser.parse(null);
}
});
}
@Test
public void parse_shouldThrowExceptionForEmptyInput() {
final InputStream emptyInput = new ByteArrayInputStream(new byte[0]);
assertThrows(
IllegalStateException.class,
new ThrowingRunnable() {
@Override
public void run() {
metadataParser.parse(emptyInput);
}
});
}
@Test
public void parse_shouldThrowExceptionForInvalidInput() {
final InputStream invalidInput = new ByteArrayInputStream("Some random input".getBytes(UTF_8));
assertThrows(
IllegalStateException.class,
new ThrowingRunnable() {
@Override
public void run() {
metadataParser.parse(invalidInput);
}
});
}
@Test
public void parse_shouldParseValidInput() throws IOException {
InputStream input = PhoneMetadataCollectionUtil.toInputStream(
PhoneMetadataCollection.newBuilder()
.addMetadata(PhoneMetadata.newBuilder().setId("id").build()));
Collection<PhoneMetadata> actual = metadataParser.parse(input);
assertEquals(1, actual.size());
}
@Test
public void parse_shouldReturnEmptyCollectionForNullInput() {
Collection<PhoneMetadata> actual = MetadataParser.newLenientParser().parse(null);
assertTrue(actual.isEmpty());
}
}

+ 117
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/BlockingMetadataBootstrappingGuardTest.java View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2022 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.metadata.source;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.i18n.phonenumbers.MetadataLoader;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadataCollection;
import com.google.i18n.phonenumbers.metadata.PhoneMetadataCollectionUtil;
import com.google.i18n.phonenumbers.metadata.init.MetadataParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@RunWith(JUnit4.class)
public class BlockingMetadataBootstrappingGuardTest {
private static final String PHONE_METADATA_FILE = "some metadata file";
private static final PhoneMetadataCollection PHONE_METADATA =
PhoneMetadataCollection.newBuilder()
.addMetadata(PhoneMetadata.newBuilder().setId("id").build());
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
@Mock private MetadataLoader metadataLoader;
@Mock private MetadataContainer metadataContainer;
private BlockingMetadataBootstrappingGuard<MetadataContainer> bootstrappingGuard;
@Before
public void setUp() throws IOException {
when(metadataLoader.loadMetadata(PHONE_METADATA_FILE))
.thenReturn(PhoneMetadataCollectionUtil.toInputStream(PHONE_METADATA));
bootstrappingGuard =
new BlockingMetadataBootstrappingGuard<>(
metadataLoader, MetadataParser.newStrictParser(), metadataContainer);
}
@Test
public void getOrBootstrap_shouldInvokeBootstrappingOnlyOnce() {
bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE);
bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE);
verify(metadataLoader, times(1)).loadMetadata(PHONE_METADATA_FILE);
verify(metadataContainer, only()).accept(any(PhoneMetadata.class));
}
@Test
public void getOrBootstrap_shouldIncludeFileNameInExceptionOnFailure() {
when(metadataLoader.loadMetadata(PHONE_METADATA_FILE)).thenReturn(null);
ThrowingRunnable throwingRunnable =
new ThrowingRunnable() {
@Override
public void run() {
bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE);
}
};
IllegalStateException exception = assertThrows(IllegalStateException.class, throwingRunnable);
Assert.assertTrue(exception.getMessage().contains(PHONE_METADATA_FILE));
}
@Test
public void getOrBootstrap_shouldInvokeBootstrappingOnlyOnceWhenThreadsCallItAtTheSameTime()
throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
List<BootstrappingRunnable> runnables = new ArrayList<>();
runnables.add(new BootstrappingRunnable());
runnables.add(new BootstrappingRunnable());
executorService.invokeAll(runnables);
verify(metadataLoader, times(1)).loadMetadata(PHONE_METADATA_FILE);
verify(metadataContainer, only()).accept(any(PhoneMetadata.class));
}
private class BootstrappingRunnable implements Callable<MetadataContainer> {
@Override
public MetadataContainer call() {
return bootstrappingGuard.getOrBootstrap(PHONE_METADATA_FILE);
}
}
}

+ 78
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/CompositeMetadataContainerTest.java View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2022 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.metadata.source;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import com.google.i18n.phonenumbers.internal.GeoEntityUtility;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class CompositeMetadataContainerTest {
private static final String REGION_CODE = "US";
private static final Integer COUNTRY_CODE = 1;
private static final PhoneMetadata PHONE_METADATA_WITH_REGION_CODE =
PhoneMetadata.newBuilder().setId(REGION_CODE).setCountryCode(COUNTRY_CODE);
private static final PhoneMetadata PHONE_METADATA_WITH_COUNTRY_CODE =
PhoneMetadata.newBuilder()
.setId(GeoEntityUtility.REGION_CODE_FOR_NON_GEO_ENTITIES)
.setCountryCode(COUNTRY_CODE);
private CompositeMetadataContainer metadataContainer;
@Before
public void setUp() {
metadataContainer = new CompositeMetadataContainer();
}
@Test
public void getMetadataBy_shouldReturnNullForNonExistingRegionCode() {
assertNull(metadataContainer.getMetadataBy(REGION_CODE));
}
@Test
public void getMetadataBy_shouldReturnMetadataForExistingRegionCode() {
metadataContainer.accept(PHONE_METADATA_WITH_REGION_CODE);
assertSame(PHONE_METADATA_WITH_REGION_CODE, metadataContainer.getMetadataBy(REGION_CODE));
}
@Test
public void getMetadataBy_shouldReturnNullForNonExistingCountryCode() {
assertNull(metadataContainer.getMetadataBy(COUNTRY_CODE));
}
@Test
public void getMetadataBy_shouldReturnMetadataForExistingCountryCode() {
metadataContainer.accept(PHONE_METADATA_WITH_COUNTRY_CODE);
assertSame(PHONE_METADATA_WITH_COUNTRY_CODE, metadataContainer.getMetadataBy(COUNTRY_CODE));
}
@Test
public void getMetadataBy_shouldReturnNullForExistingCountryCodeOfGeoRegion() {
metadataContainer.accept(PHONE_METADATA_WITH_REGION_CODE);
assertNull(metadataContainer.getMetadataBy(COUNTRY_CODE));
}
}

+ 74
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MapBackedMetadataContainerTest.java View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2022 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.metadata.source;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class MapBackedMetadataContainerTest {
private static final String REGION_CODE = "US";
private static final Integer COUNTRY_CODE = 41;
private static final PhoneMetadata PHONE_METADATA =
PhoneMetadata.newBuilder().setId(REGION_CODE).setCountryCode(COUNTRY_CODE);
@Test
public void getMetadataBy_shouldReturnNullForNullRegionCode() {
assertNull(MapBackedMetadataContainer.byRegionCode().getMetadataBy(null));
}
@Test
public void getMetadataBy_shouldReturnNullForNonExistingRegionCode() {
assertNull(MapBackedMetadataContainer.byRegionCode().getMetadataBy(REGION_CODE));
}
@Test
public void getMetadataBy_shouldReturnMetadataForExistingRegionCode() {
MapBackedMetadataContainer<String> metadataContainer =
MapBackedMetadataContainer.byRegionCode();
metadataContainer.accept(PHONE_METADATA);
assertSame(PHONE_METADATA, metadataContainer.getMetadataBy(REGION_CODE));
}
@Test
public void getMetadataBy_shouldReturnNullForNullCountryCode() {
assertNull(MapBackedMetadataContainer.byCountryCallingCode().getMetadataBy(null));
}
@Test
public void getMetadataBy_shouldReturnNullForNonExistingCountryCode() {
assertNull(MapBackedMetadataContainer.byCountryCallingCode().getMetadataBy(COUNTRY_CODE));
}
@Test
public void getMetadataBy_shouldReturnMetadataForExistingCountryCode() {
MapBackedMetadataContainer<Integer> metadataContainer =
MapBackedMetadataContainer.byCountryCallingCode();
metadataContainer.accept(PHONE_METADATA);
assertSame(PHONE_METADATA, metadataContainer.getMetadataBy(COUNTRY_CODE));
}
}

+ 51
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/MultiFileModeFileNameProviderTest.java View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2022 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.metadata.source;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class MultiFileModeFileNameProviderTest {
private final PhoneMetadataFileNameProvider metadataFileNameProvider =
new MultiFileModeFileNameProvider("some/file");
@Test
public void getFor_shouldAppendKeyToTheBase() {
String metadataFileName = metadataFileNameProvider.getFor("key1");
assertEquals("some/file_key1", metadataFileName);
}
@Test
public void getFor_shouldThrowExceptionForNonAlphanumericKey() {
assertThrows(
IllegalArgumentException.class,
new ThrowingRunnable() {
@Override
public void run() {
metadataFileNameProvider.getFor("\tkey1\n");
}
});
}
}

+ 37
- 0
java/libphonenumber/test/com/google/i18n/phonenumbers/metadata/source/SingleFileModeFileNameProviderTest.java View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 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.metadata.source;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class SingleFileModeFileNameProviderTest {
private final PhoneMetadataFileNameProvider metadataFileNameProvider =
new SingleFileModeFileNameProvider("some/file");
@Test
public void getFor_shouldReturnTheFileNameBase() {
String metadataFileName = metadataFileNameProvider.getFor("key1");
assertEquals("some/file", metadataFileName);
}
}

+ 6
- 0
java/pom.xml View File

@ -235,6 +235,12 @@
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.3.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

+ 3
- 3
tools/java/java-build/src/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java View File

@ -254,7 +254,7 @@ public class BuildMetadataProtoFromXml extends Command {
writer.addToImports("java.util.List");
writer.addToImports("java.util.Map");
writer.addToBody(" static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {\n");
writer.addToBody(" public static Map<Integer, List<String>> getCountryCodeToRegionCodeMap() {\n");
writer.formatToBody(CAPACITY_COMMENT, capacity, countryCodeToRegionCodeMap.size());
writer.addToBody(" Map<Integer, List<String>> countryCodeToRegionCodeMap =\n");
writer.addToBody(" new HashMap<Integer, List<String>>(" + capacity + ");\n");
@ -286,7 +286,7 @@ public class BuildMetadataProtoFromXml extends Command {
writer.addToImports("java.util.HashSet");
writer.addToImports("java.util.Set");
writer.addToBody(" static Set<String> getRegionCodeSet() {\n");
writer.addToBody(" public static Set<String> getRegionCodeSet() {\n");
writer.formatToBody(CAPACITY_COMMENT, capacity, regionCodeList.size());
writer.addToBody(" Set<String> regionCodeSet = new HashSet<String>(" + capacity + ");\n");
writer.addToBody("\n");
@ -307,7 +307,7 @@ public class BuildMetadataProtoFromXml extends Command {
writer.addToImports("java.util.HashSet");
writer.addToImports("java.util.Set");
writer.addToBody(" static Set<Integer> getCountryCodeSet() {\n");
writer.addToBody(" public static Set<Integer> getCountryCodeSet() {\n");
writer.formatToBody(CAPACITY_COMMENT, capacity, countryCodeSet.size());
writer.addToBody(" Set<Integer> countryCodeSet = new HashSet<Integer>(" + capacity + ");\n");
writer.addToBody("\n");


Loading…
Cancel
Save