diff --git a/java/libphonenumber/pom.xml b/java/libphonenumber/pom.xml
index 9992f6454..4ff8497d0 100644
--- a/java/libphonenumber/pom.xml
+++ b/java/libphonenumber/pom.xml
@@ -36,6 +36,8 @@
**/SingleFileMetadataSourceImpl.class
**/SingleFileMetadataSourceImpl.java
+ **/SingleFileMetadataSourceImplTest.class
+ **/SingleFileMetadataSourceImplTest.java
**/SingleFilePhoneNumberMetadataProto
**/SingleFilePhoneNumberMetadataProtoForTesting
diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataSource.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataSource.java
index bedd570f3..d0ec50dfe 100644
--- a/java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataSource.java
+++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/MetadataSource.java
@@ -19,7 +19,7 @@ package com.google.i18n.phonenumbers;
import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata;
/**
- * A source for phone metadata from resources.
+ * A source for phone metadata for all regions.
*/
interface MetadataSource {
/**
diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java
index 22a08c06f..09114add4 100644
--- a/java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java
+++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java
@@ -129,8 +129,7 @@ final class MultiFileMetadataSourceImpl implements MetadataSource {
/**
* 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).
+ * exceptions that occur while reading or closing the stream are ignored.
*
* @param source the non-null stream from which metadata is to be read.
* @return the loaded metadata protocol buffer.
diff --git a/java/libphonenumber/src/com/google/i18n/phonenumbers/SingleFileMetadataSourceImpl.java b/java/libphonenumber/src/com/google/i18n/phonenumbers/SingleFileMetadataSourceImpl.java
index 289c708ae..82297153c 100644
--- a/java/libphonenumber/src/com/google/i18n/phonenumbers/SingleFileMetadataSourceImpl.java
+++ b/java/libphonenumber/src/com/google/i18n/phonenumbers/SingleFileMetadataSourceImpl.java
@@ -1,3 +1,19 @@
+/*
+ * 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;
@@ -7,23 +23,21 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.Collections;
-import java.util.EnumMap;
import java.util.HashMap;
-import java.util.Iterator;
+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 a resource file
- * during initialization.
+ * Implementation of {@link MetadataSource} that reads from a single resource file.
*/
-public final class SingleFileMetadataSourceImpl implements MetadataSource {
+final class SingleFileMetadataSourceImpl implements MetadataSource {
private static final Logger logger =
Logger.getLogger(SingleFileMetadataSourceImpl.class.getName());
- private static final String META_DATA_FILE =
+ private static final String META_DATA_FILE_NAME =
"/com/google/i18n/phonenumbers/data/SingleFilePhoneNumberMetadataProto";
// A mapping from a region code to the PhoneMetadata for that region.
@@ -40,57 +54,95 @@ public final class SingleFileMetadataSourceImpl implements MetadataSource {
private final Map countryCodeToNonGeographicalMetadataMap =
Collections.synchronizedMap(new HashMap());
+ // The metadata file from which region data is loaded.
+ private final String fileName;
+
+ // The metadata loader used to inject alternative metadata sources.
+ private final MetadataLoader metadataLoader;
+
+ // It is assumed that metadataLoader is not null.
+ public SingleFileMetadataSourceImpl(String fileName, MetadataLoader metadataLoader) {
+ this.fileName = fileName;
+ this.metadataLoader = metadataLoader;
+ }
+
// It is assumed that metadataLoader is not null.
public SingleFileMetadataSourceImpl(MetadataLoader metadataLoader) {
- InputStream input = metadataLoader.loadMetadata(META_DATA_FILE);
- if (input == null) {
- throw new IllegalStateException(
- "no metadata available for PhoneNumberUtil: " + META_DATA_FILE);
- }
- PhoneMetadataCollection metadataCollection = loadMetadataAndCloseInput(input);
- for (PhoneMetadata metadata : metadataCollection.getMetadataList()) {
- String regionCode = metadata.getId();
- int countryCallingCode = metadata.getCountryCode();
- if (PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode)) {
- countryCodeToNonGeographicalMetadataMap.put(countryCallingCode, metadata);
- } else {
- regionToMetadataMap.put(regionCode, metadata);
- }
- }
+ this(META_DATA_FILE_NAME, 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();
+ }
+ }
return regionToMetadataMap.get(regionCode);
}
@Override
public PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) {
+ synchronized (countryCodeToNonGeographicalMetadataMap) {
+ if (!countryCodeToNonGeographicalMetadataMap.containsKey(countryCallingCode)) {
+ loadMetadataFromFile();
+ }
+ }
return countryCodeToNonGeographicalMetadataMap.get(countryCallingCode);
}
+ // @VisibleForTesting
+ void loadMetadataFromFile() {
+ 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);
+ }
+ for (PhoneMetadata metadata : metadataList) {
+ String regionCode = metadata.getId();
+ int countryCallingCode = metadata.getCountryCode();
+ boolean isNonGeoRegion = PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCode);
+ 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).
+ * exceptions that occur while reading or closing the stream are ignored.
*
* @param source the non-null stream from which metadata is to be read.
* @return the loaded metadata protocol buffer.
*/
- private static PhoneMetadataCollection loadMetadataAndCloseInput(InputStream source) {
+ private static PhoneMetadataCollection loadMetadataAndCloseInput(ObjectInputStream source) {
PhoneMetadataCollection metadataCollection = new PhoneMetadataCollection();
try {
- // Read in metadata for each region.
- ObjectInputStream in = new ObjectInputStream(source);
- metadataCollection.readExternal(in);
- return metadataCollection;
+ metadataCollection.readExternal(source);
} catch (IOException e) {
- logger.log(Level.WARNING, e.toString());
+ logger.log(Level.WARNING, "error reading input (ignored)", e);
} finally {
try {
source.close();
} catch (IOException e) {
- logger.log(Level.WARNING, e.toString());
+ logger.log(Level.WARNING, "error closing input stream (ignored)", e);
}
}
return metadataCollection;
diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java
index ea45c3920..c1c531910 100644
--- a/java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java
+++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/MultiFileMetadataSourceImplTest.java
@@ -16,19 +16,17 @@
package com.google.i18n.phonenumbers;
+import junit.framework.TestCase;
+
/**
* Unit tests for MultiFileMetadataSourceImpl.java.
*/
-public class MultiFileMetadataSourceImplTest extends TestMetadataTestCase {
-
- private final MultiFileMetadataSourceImpl multiFileMetadataSource;
-
- public MultiFileMetadataSourceImplTest() {
- multiFileMetadataSource = new MultiFileMetadataSourceImpl(
- "no/such/file", PhoneNumberUtil.DEFAULT_METADATA_LOADER);
- }
+public class MultiFileMetadataSourceImplTest extends TestCase {
+ public MultiFileMetadataSourceImplTest() {}
public void testMissingMetadataFileThrowsRuntimeException() {
+ MultiFileMetadataSourceImpl multiFileMetadataSource = new MultiFileMetadataSourceImpl(
+ "no/such/file", PhoneNumberUtil.DEFAULT_METADATA_LOADER);
// 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.
@@ -36,7 +34,7 @@ public class MultiFileMetadataSourceImplTest extends TestMetadataTestCase {
multiFileMetadataSource.loadMetadataFromFile("XX", -1);
fail("expected exception");
} catch (RuntimeException e) {
- assertTrue("Unexpected error: " + e, e.toString().contains("no/such/file_XX"));
+ assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file_XX"));
}
try {
multiFileMetadataSource.loadMetadataFromFile(
diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/SingleFileMetadataSourceImplTest.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/SingleFileMetadataSourceImplTest.java
new file mode 100644
index 000000000..5b7f13036
--- /dev/null
+++ b/java/libphonenumber/test/com/google/i18n/phonenumbers/SingleFileMetadataSourceImplTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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 junit.framework.TestCase;
+
+/**
+ * Unit tests for SingleFileMetadataSourceImpl.java.
+ */
+public class SingleFileMetadataSourceImplTest extends TestCase {
+ public SingleFileMetadataSourceImplTest() {}
+
+ public void testMissingMetadataFileThrowsRuntimeException() {
+ SingleFileMetadataSourceImpl singleFileMetadataSource = new SingleFileMetadataSourceImpl(
+ "no/such/file", PhoneNumberUtil.DEFAULT_METADATA_LOADER);
+ // 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 {
+ singleFileMetadataSource.loadMetadataFromFile();
+ fail("expected exception");
+ } catch (RuntimeException e) {
+ assertTrue("Unexpected error: " + e, e.getMessage().contains("no/such/file"));
+ }
+ }
+}
diff --git a/java/libphonenumber/test/com/google/i18n/phonenumbers/TestMetadataTestCase.java b/java/libphonenumber/test/com/google/i18n/phonenumbers/TestMetadataTestCase.java
index 99def5ae2..ca9014d44 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 {
- protected static final String TEST_META_DATA_FILE_PREFIX =
+ private static final String TEST_META_DATA_FILE_PREFIX =
"/com/google/i18n/phonenumbers/data/PhoneNumberMetadataProtoForTesting";
protected final PhoneNumberUtil phoneUtil;
diff --git a/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar b/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar
index d4effa093..b0fe6761a 100644
Binary files a/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar and b/tools/java/java-build/target/java-build-1.0-SNAPSHOT-jar-with-dependencies.jar differ