View Javadoc
1   /*
2    *  Copyright 2010, 2011, 2012 Christopher Pheby
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.jadira.cdt.phonenumber.impl;
17  
18  import java.io.Serializable;
19  
20  import org.jadira.bindings.core.annotation.typesafe.FromString;
21  import org.jadira.bindings.core.annotation.typesafe.ToString;
22  import org.jadira.cdt.country.CountryCode;
23  import org.jadira.cdt.country.ISOCountryCode;
24  import org.jadira.cdt.phonenumber.api.PhoneNumber;
25  import org.jadira.cdt.phonenumber.api.PhoneNumberParseException;
26  
27  import com.google.i18n.phonenumbers.NumberParseException;
28  import com.google.i18n.phonenumbers.PhoneNumberUtil;
29  import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
30  import com.google.i18n.phonenumbers.Phonenumber;
31  
32  /**
33   * This class represents a phone number with a broadly canonical rendering.
34   * The default form is to use E164 format with the addition of an optional extension.
35   * Extension is signified with a suffix of ";ext=".
36   * 
37   * The class uses Google's excellent libphonenumber for its underlying representation.
38   * This gives it great flexibility in accomodating existing legacy number formats and
39   * converting them to a standardised notation.
40   * 
41   * The class does not expose directly the wrapped PhoneNumber instance as it is 
42   * immutable. However, it is possible to retrieve a copy for further manipulation.
43   */
44  public class E164PhoneNumberWithExtension implements PhoneNumber, Serializable {
45  
46  	private static final long serialVersionUID = -7665314825162464754L;
47  
48  	private static final PhoneNumberUtil PHONE_NUMBER_UTIL = PhoneNumberUtil.getInstance();
49      
50      private static final String RFC3966_EXTN_PREFIX = ";ext=";
51  
52      private Phonenumber.PhoneNumber number;
53  
54  	private static final String EX_PARSE_MSG_PREFIX = "Could not parse {";
55      
56      /**
57       * Creates a instance from the given PhoneNumber
58       * @param prototype The PhoneNumber to construct the instance from
59       */
60      public E164PhoneNumberWithExtension(Phonenumber.PhoneNumber prototype) {
61  
62          StringBuilder e164Builder = new StringBuilder();
63          PHONE_NUMBER_UTIL.format(prototype, PhoneNumberFormat.E164, e164Builder);
64          
65      	try {
66              this.number = PHONE_NUMBER_UTIL.parse(e164Builder.toString(), "GB");
67          } catch (NumberParseException e) {
68              throw new PhoneNumberParseException(
69                      EX_PARSE_MSG_PREFIX + prototype.getNationalNumber() +"}", e);
70          }
71      	
72      	if (prototype.hasExtension()) {
73      		this.number.setExtension(prototype.getExtension());
74      	}
75      }
76  
77      /**
78       * Creates a new E164 Phone Number with the given extension.
79       * @param e164PhoneNumberWithExtension The phone number in E164 format. The extension may be appended.  
80       * Any extension is appended to the number with the extension prefix given as ';ext='
81       */
82      protected E164PhoneNumberWithExtension(String e164PhoneNumberWithExtension) {
83  
84          if (!e164PhoneNumberWithExtension.startsWith("+")) {
85              throw new PhoneNumberParseException("Only international numbers can be interpreted without a country code");
86          }
87          
88          final String e164PhoneNumber;
89          final String extension;
90          if (e164PhoneNumberWithExtension.contains(RFC3966_EXTN_PREFIX)) {
91          	extension = e164PhoneNumberWithExtension.substring(e164PhoneNumberWithExtension.indexOf(RFC3966_EXTN_PREFIX) + RFC3966_EXTN_PREFIX.length());
92          	e164PhoneNumber = e164PhoneNumberWithExtension.substring(0, e164PhoneNumberWithExtension.indexOf(RFC3966_EXTN_PREFIX));
93          } else {
94          	extension = null;
95          	e164PhoneNumber = e164PhoneNumberWithExtension;
96          }
97  
98          try {
99              number = PHONE_NUMBER_UTIL.parse(e164PhoneNumber, "GB");
100         } catch (NumberParseException e) {
101             throw new PhoneNumberParseException(
102                     EX_PARSE_MSG_PREFIX + e164PhoneNumber +"}", e);
103         }
104         
105         if (extension != null) {
106         	number.setExtension(extension);
107         }
108     }
109 
110     /**
111      * Creates a new E164 Phone Number with the given extension.
112      * @param e164PhoneNumber The phone number in E164 format
113      * @param extension The extension, or null for no extension
114      */
115     protected E164PhoneNumberWithExtension(String e164PhoneNumber, String extension) {
116 
117         if (!e164PhoneNumber.startsWith("+")) {
118             throw new PhoneNumberParseException("Only international numbers can be interpreted without a country code");
119         }
120         
121         try {
122             number = PHONE_NUMBER_UTIL.parse(e164PhoneNumber, "GB");
123         } catch (NumberParseException e) {
124             throw new PhoneNumberParseException(
125                     EX_PARSE_MSG_PREFIX + e164PhoneNumber +"}", e);
126         }
127         
128         number.setExtension(extension);
129     }
130     
131     /**
132      * Creates a new E164 Phone Number.
133      * @param phoneNumber The phone number in arbitrary parseable format (may be a national format)
134      * @param defaultCountryCode The Country to apply if no country is indicated by the phone number
135      */
136     protected E164PhoneNumberWithExtension(String phoneNumber, CountryCode defaultCountryCode) {
137 
138         try {
139             number = PHONE_NUMBER_UTIL.parse(phoneNumber, defaultCountryCode.toString());
140         } catch (NumberParseException e) {
141             throw new PhoneNumberParseException(
142                     EX_PARSE_MSG_PREFIX + phoneNumber + "} for country {" + defaultCountryCode +"}", e);
143         }
144     }
145 
146     /**
147      * Creates a new E164 Phone Number with the given extension.
148      * @param phoneNumber The phone number in arbitrary parseable format (may be a national format)
149      * @param extension The extension, or null for no extension
150      * @param defaultCountryCode The Country to apply if no country is indicated by the phone number
151      */
152     protected E164PhoneNumberWithExtension(String phoneNumber, String extension, CountryCode defaultCountryCode) {
153 
154         try {
155             number = PHONE_NUMBER_UTIL.parse(phoneNumber, defaultCountryCode.toString());
156         } catch (NumberParseException e) {
157             throw new PhoneNumberParseException(
158                     EX_PARSE_MSG_PREFIX + phoneNumber + "} for country {" + defaultCountryCode +"}", e);
159         }
160         if (extension != null) {
161         	number.setExtension(extension);
162         }
163     }
164 
165     /**
166      * Creates a new E164 Phone Number.
167      * @param e164PhoneNumber The phone number in E164 format.
168      * @return A new instance of E164PhoneNumberWithExtension
169      */
170     public static E164PhoneNumberWithExtension ofE164PhoneNumberString(String e164PhoneNumber) {
171     	return new E164PhoneNumberWithExtension(e164PhoneNumber, (String)null);
172     }
173 
174     /**
175      * Creates a new E164 Phone Number with the given extension.
176      * @param e164PhoneNumber The phone number in E164 format. The extension may be appended.  
177      * Any extension is appended to the number with the extension prefix given as ';ext='
178      * @return A new instance of E164PhoneNumberWithExtension
179      */
180     @FromString
181     public static E164PhoneNumberWithExtension ofE164PhoneNumberWithExtensionString(String e164PhoneNumber) {
182     	return new E164PhoneNumberWithExtension(e164PhoneNumber);
183     }
184 
185     /**
186      * Creates a new E164 Phone Number with the given extension.
187      * @param e164PhoneNumber The phone number in E164 format
188      * @param extension The extension, or null for no extension
189      * @return A new instance of E164PhoneNumberWithExtension
190      */
191     public static E164PhoneNumberWithExtension ofE164PhoneNumberStringAndExtension(String e164PhoneNumber, String extension) {
192     	return new E164PhoneNumberWithExtension(e164PhoneNumber, extension);
193     }
194     
195     /**
196      * Creates a new E164 Phone Number.
197      * @param phoneNumber The phone number in arbitrary parseable format (may be a national format)
198      * @param defaultCountryCode The Country to apply if no country is indicated by the phone number
199      * @return A new instance of E164PhoneNumberWithExtension
200      */
201     public static E164PhoneNumberWithExtension ofPhoneNumberString(String phoneNumber, CountryCode defaultCountryCode) {
202     	return new E164PhoneNumberWithExtension(phoneNumber, defaultCountryCode);
203     }
204     
205     /**
206      * Creates a new E164 Phone Number with the given extension.
207      * @param phoneNumber The phone number in arbitrary parseable format (may be a national format)
208      * @param extension The extension, or null for no extension
209      * @param defaultCountryCode The Country to apply if no country is indicated by the phone number
210      * @return A new instance of E164PhoneNumberWithExtension
211      */
212     public static E164PhoneNumberWithExtension ofPhoneNumberStringAndExtension(String phoneNumber, String extension, CountryCode defaultCountryCode) {
213     	return new E164PhoneNumberWithExtension(phoneNumber, extension, defaultCountryCode);
214     }
215     
216     /**
217      * Returns the underlying LibPhoneNumber {@link Phonenumber.PhoneNumber} instance. 
218      * To preserve the immutability of E164PhoneNumber, a copy is made.
219      * @return The underlying PhoneNumber instance
220      */
221     public Phonenumber.PhoneNumber getUnderlyingPhoneNumber() {
222         
223     	Phonenumber.PhoneNumber copy;
224     	
225         StringBuilder e164Builder = new StringBuilder();
226         PHONE_NUMBER_UTIL.format(number, PhoneNumberFormat.E164, e164Builder);
227         
228     	try {
229             copy = PHONE_NUMBER_UTIL.parse(e164Builder.toString(), "GB");
230         } catch (NumberParseException e) {
231             throw new PhoneNumberParseException(
232                     EX_PARSE_MSG_PREFIX + number.getNationalNumber() +"}", e);
233         }
234     	
235     	if (number.hasExtension()) {
236     		copy.setExtension(number.getExtension());
237     	}
238     	return copy;
239     }
240 
241 	/**
242 	 * {@inheritDoc}
243 	 */
244     public ISOCountryCode extractCountryCode() {
245         return ISOCountryCode.valueOf(PHONE_NUMBER_UTIL.getRegionCodeForNumber(number));
246     }
247 
248 	/**
249 	 * {@inheritDoc}
250 	 */
251     public String getCountryDiallingCode() {
252         return "+" + number.getCountryCode();
253     }
254 
255 	/**
256 	 * {@inheritDoc}
257 	 */
258     public String getNationalNumber() {
259         return "" + number.getNationalNumber();
260     }
261 
262 	/**
263 	 * {@inheritDoc}
264 	 */
265     public String getExtension() {
266         return number.hasExtension() ? number.getExtension() : null;
267     }
268 
269 	/**
270 	 * {@inheritDoc}
271 	 */
272     public String toE164NumberString() {
273         StringBuilder result = new StringBuilder();
274         PHONE_NUMBER_UTIL.format(number, PhoneNumberFormat.E164, result);
275         
276         return result.toString();
277     }
278     
279 	/**
280 	 * {@inheritDoc}
281 	 */
282     @ToString
283     public String toE164NumberWithExtensionString() {
284     	
285     	StringBuilder formattedString = new StringBuilder(toE164NumberString());
286     	
287     	if(number.hasExtension()) {
288     		formattedString.append(RFC3966_EXTN_PREFIX);
289     		formattedString.append(number.getExtension());
290     	}
291     	
292     	return formattedString.toString();
293     }
294     
295 	/**
296 	 * {@inheritDoc}
297 	 */
298     public String extractAreaCode() {
299         
300 		final String nationalSignificantNumber = PHONE_NUMBER_UTIL.getNationalSignificantNumber(number);
301 		final String areaCode;
302 
303 		int areaCodeLength = PHONE_NUMBER_UTIL.getLengthOfGeographicalAreaCode(number);
304 		if (areaCodeLength > 0) {
305 			areaCode = nationalSignificantNumber.substring(0, areaCodeLength);
306 		} else {
307 			areaCode = "";
308 		}
309 
310 		return areaCode;
311     }
312 
313 	/**
314 	 * {@inheritDoc}
315 	 */
316     public String extractSubscriberNumber() {
317         
318 		final String nationalSignificantNumber = PHONE_NUMBER_UTIL.getNationalSignificantNumber(number);
319 		final String subscriberNumber;
320 
321 		int areaCodeLength = PHONE_NUMBER_UTIL.getLengthOfGeographicalAreaCode(number);
322 		if (areaCodeLength > 0) {
323 			subscriberNumber = nationalSignificantNumber.substring(areaCodeLength);
324 		} else {
325 			subscriberNumber = nationalSignificantNumber;
326 		}
327 
328 		return subscriberNumber;
329     }    
330 
331 	/**
332 	 * {@inheritDoc}
333 	 */
334     @Override
335     public String toString() {
336     	return toE164NumberWithExtensionString();
337     }
338     
339 	/**
340 	 * {@inheritDoc}
341 	 */
342 	@Override
343 	public boolean equals(Object obj) {
344 		if (obj == null) {
345 			return false;
346 		}
347 		if (!this.getClass().equals(obj.getClass())) {
348 			return false;
349 		}
350 
351 		final E164PhoneNumberWithExtension obj2 = (E164PhoneNumberWithExtension) obj;
352 		if (this.toString().equals(obj2.toString())) {
353 			return true;
354 		}
355 
356 		return false;
357 	}
358 
359 	/**
360 	 * {@inheritDoc}
361 	 */
362 	@Override
363 	public int hashCode() {
364 		return toString().hashCode();
365 	}
366 }