001/*
002 *  Copyright 2010, 2011, 2012 Christopher Pheby
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.jadira.cdt.phonenumber.impl;
017
018import java.io.Serializable;
019
020import org.jadira.bindings.core.annotation.typesafe.FromString;
021import org.jadira.bindings.core.annotation.typesafe.ToString;
022import org.jadira.cdt.country.CountryCode;
023import org.jadira.cdt.country.ISOCountryCode;
024import org.jadira.cdt.phonenumber.api.PhoneNumber;
025import org.jadira.cdt.phonenumber.api.PhoneNumberParseException;
026
027import com.google.i18n.phonenumbers.NumberParseException;
028import com.google.i18n.phonenumbers.PhoneNumberUtil;
029import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
030import com.google.i18n.phonenumbers.Phonenumber;
031
032/**
033 * This class represents a phone number with a broadly canonical rendering.
034 * The default form is to use E164 format with the addition of an optional extension.
035 * Extension is signified with a suffix of ";ext=".
036 * 
037 * The class uses Google's excellent libphonenumber for its underlying representation.
038 * This gives it great flexibility in accomodating existing legacy number formats and
039 * converting them to a standardised notation.
040 * 
041 * The class does not expose directly the wrapped PhoneNumber instance as it is 
042 * immutable. However, it is possible to retrieve a copy for further manipulation.
043 */
044public class E164PhoneNumberWithExtension implements PhoneNumber, Serializable {
045
046        private static final long serialVersionUID = -7665314825162464754L;
047
048        private static final PhoneNumberUtil PHONE_NUMBER_UTIL = PhoneNumberUtil.getInstance();
049    
050    private static final String RFC3966_EXTN_PREFIX = ";ext=";
051
052    private Phonenumber.PhoneNumber number;
053
054        private static final String EX_PARSE_MSG_PREFIX = "Could not parse {";
055    
056    /**
057     * Creates a instance from the given PhoneNumber
058     * @param prototype The PhoneNumber to construct the instance from
059     */
060    public E164PhoneNumberWithExtension(Phonenumber.PhoneNumber prototype) {
061
062        StringBuilder e164Builder = new StringBuilder();
063        PHONE_NUMBER_UTIL.format(prototype, PhoneNumberFormat.E164, e164Builder);
064        
065        try {
066            this.number = PHONE_NUMBER_UTIL.parse(e164Builder.toString(), "GB");
067        } catch (NumberParseException e) {
068            throw new PhoneNumberParseException(
069                    EX_PARSE_MSG_PREFIX + prototype.getNationalNumber() +"}", e);
070        }
071        
072        if (prototype.hasExtension()) {
073                this.number.setExtension(prototype.getExtension());
074        }
075    }
076
077    /**
078     * Creates a new E164 Phone Number with the given extension.
079     * @param e164PhoneNumberWithExtension The phone number in E164 format. The extension may be appended.  
080     * Any extension is appended to the number with the extension prefix given as ';ext='
081     */
082    protected E164PhoneNumberWithExtension(String e164PhoneNumberWithExtension) {
083
084        if (!e164PhoneNumberWithExtension.startsWith("+")) {
085            throw new PhoneNumberParseException("Only international numbers can be interpreted without a country code");
086        }
087        
088        final String e164PhoneNumber;
089        final String extension;
090        if (e164PhoneNumberWithExtension.contains(RFC3966_EXTN_PREFIX)) {
091                extension = e164PhoneNumberWithExtension.substring(e164PhoneNumberWithExtension.indexOf(RFC3966_EXTN_PREFIX) + RFC3966_EXTN_PREFIX.length());
092                e164PhoneNumber = e164PhoneNumberWithExtension.substring(0, e164PhoneNumberWithExtension.indexOf(RFC3966_EXTN_PREFIX));
093        } else {
094                extension = null;
095                e164PhoneNumber = e164PhoneNumberWithExtension;
096        }
097
098        try {
099            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}