summaryrefslogtreecommitdiff
path: root/libphonenumber/src/com/google/i18n/phonenumbers/MultiFileMetadataSourceImpl.java
blob: d89052c8009a9f5ce3bf21e61e4f76a07ec832b8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
 * 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.InputStream;
import java.io.ObjectInputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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.
  private final ConcurrentHashMap<String, PhoneMetadata> geographicalRegions =
      new ConcurrentHashMap<String, PhoneMetadata>();

  // 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).
  private final ConcurrentHashMap<Integer, PhoneMetadata> nonGeographicalRegions =
      new ConcurrentHashMap<Integer, PhoneMetadata>();

  // The prefix of the metadata files from which region data is loaded.
  private final String filePrefix;

  // The metadata loader used to inject alternative metadata sources.
  private final MetadataLoader metadataLoader;

  // It is assumed that metadataLoader is not null. If needed, checks should happen before passing
  // here.
  // @VisibleForTesting
  MultiFileMetadataSourceImpl(String filePrefix, MetadataLoader metadataLoader) {
    this.filePrefix = filePrefix;
    this.metadataLoader = metadataLoader;
  }

  // It is assumed that metadataLoader is not null. If needed, checks should happen before passing
  // here.
  public MultiFileMetadataSourceImpl(MetadataLoader metadataLoader) {
    this(META_DATA_FILE_PREFIX, metadataLoader);
  }

  @Override
  public PhoneMetadata getMetadataForRegion(String regionCode) {
    PhoneMetadata metadata = geographicalRegions.get(regionCode);
    return (metadata != null) ? metadata : loadMetadataFromFile(
        regionCode, geographicalRegions, filePrefix, metadataLoader);
  }

  @Override
  public PhoneMetadata getMetadataForNonGeographicalRegion(int countryCallingCode) {
    PhoneMetadata metadata = nonGeographicalRegions.get(countryCallingCode);
    if (metadata != null) {
      return metadata;
    }
    if (isNonGeographical(countryCallingCode)) {
      return loadMetadataFromFile(
          countryCallingCode, nonGeographicalRegions, filePrefix, metadataLoader);
    }
    // The given country calling code was for a geographical region.
    return null;
  }

  // A country calling code is non-geographical if it only maps to the non-geographical region code,
  // i.e. "001".
  private boolean isNonGeographical(int countryCallingCode) {
    List<String> regionCodes =
        CountryCodeToRegionCodeMap.getCountryCodeToRegionCodeMap().get(countryCallingCode);
    return (regionCodes.size() == 1
        && PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY.equals(regionCodes.get(0)));
  }

  /**
   * @param key             The geographical region code or the non-geographical region's country
   *                        calling code.
   * @param map             The map to contain the mapping from {@code key} to the corresponding
   *                        metadata.
   * @param filePrefix      The prefix of the metadata files from which region data is loaded.
   * @param metadataLoader  The metadata loader used to inject alternative metadata sources.
   */
  // @VisibleForTesting
  static <T> PhoneMetadata loadMetadataFromFile(
      T key, ConcurrentHashMap<T, PhoneMetadata> map, String filePrefix,
      MetadataLoader metadataLoader) {
    // We assume key.toString() is well-defined.
    String fileName = filePrefix + "_" + key;
    InputStream source = metadataLoader.loadMetadata(fileName);
    if (source == null) {
      // Sanity check; this should not happen since we only load things based on the expectation
      // that they are present, by checking the map of available data first.
      throw new IllegalStateException("missing metadata: " + fileName);
    }
    PhoneMetadataCollection metadataCollection = MetadataManager.loadMetadataAndCloseInput(source);
    List<PhoneMetadata> metadataList = metadataCollection.getMetadataList();
    if (metadataList.isEmpty()) {
      // Sanity check; this should not happen since we build with non-empty metadata.
      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);
    PhoneMetadata oldValue = map.putIfAbsent(key, metadata);
    return (oldValue != null) ? oldValue : metadata;
  }
}