Source code: com/clra/member/Telephone.java
1 /*
2 * Copyright (c) Carnegie Lake Rowing Association 2002. All rights reserved.
3 * Distributed under the GPL license. See doc/COPYING.
4 * $RCSfile: Telephone.java,v $
5 * $Date: 2003/02/26 03:38:45 $
6 * $Revision: 1.13 $
7 */
8
9 package com.clra.member;
10
11 import java.io.Serializable;
12
13 /**
14 * Encapsulates telephone information of a member.
15 * @version $Id: Telephone.java,v 1.13 2003/02/26 03:38:45 rphall Exp $
16 * @author <a href="mailto:rphall@pluto.njcc.com">Rick Hall</a>
17 */
18 public class Telephone implements Serializable {
19
20 public final static String EVENING = "evening";
21
22 public final static String DAY = "day";
23
24 public final static String OTHER = "other";
25
26 private String areaCode = null;
27 private String exchange = null;
28 private String local = null;
29 private String extension = null;
30
31 // character indices
32 private int ac1; // starting position of area code
33 private int xch0; // expected position of space before exchange
34 private int loc1; // expected start of local number
35 private int xt0; // expected position of space before optional extension
36
37 public static boolean isValidAreaCode( String areaCode ) {
38 boolean retVal = isValidComponent(areaCode);
39 retVal = retVal && areaCode.length() == 3;
40 return retVal;
41 }
42
43 public static boolean isValidExchange( String exchange ) {
44 return isValidAreaCode(exchange);
45 }
46
47 public static boolean isValidLocal( String local ) {
48 boolean retVal = isValidComponent(local);
49 retVal = retVal && local.length() == 4;
50 return retVal;
51 }
52
53 public static boolean isValidExtension( String extension ) {
54 boolean retVal;
55 if ( extension == null ) {
56 retVal = true;
57 }
58 else {
59 retVal = isValidComponent(extension);
60 retVal = retVal && extension.length() > 0;
61 }
62 return retVal;
63 }
64
65 private static boolean isValidComponent( String number ) {
66
67 boolean retVal = number != null
68 && number.trim().length() == number.length()
69 && number.length() <= 5;
70
71 for ( int i=0; retVal && i<number.length(); i++ ) {
72 retVal = Character.isDigit( number.charAt(i) );
73 }
74
75 return retVal;
76 } // isValidLocal(String)
77
78 /** Produces an invalid instance. Used only during deserialization. */
79 public Telephone() {}
80
81 public Telephone( String ac, String ech, String lcl, String ext) {
82
83 this.areaCode = ac == null ? null : ac.trim();
84 this.exchange = ech == null ? null : ech.trim();
85 this.local = lcl == null ? null : lcl.trim();
86
87 this.extension = ext == null ? null : ext.trim();
88 if ( this.extension != null && this.extension.length() == 0 ) {
89 this.extension = null;
90 }
91
92 validateComponents();
93
94 } // ctor(String,String,String,String)
95
96 /**
97 * @param number a string of the form "(123) 456-7890 Ext. 12345"
98 * or "(123)-456-7890" where<ul>
99 * <li><tt>123</tt> is the area code, OPTIONALLY enclosed in parentheses</li>
100 * <li><tt>456</tt> is the exchange</li>
101 * <li><tt>7890</tt> is the local number, and <tt>12345</tt> is an optional
102 * extension.</li></ul>
103 * The area code, exchange and local number are required, so the minimal
104 * length of a valid number is 12 (or 14) including optional parentheses, a
105 * space or dash, and a second dash.
106 */
107 public Telephone( String number ) {
108
109 // Precondition
110 if ( number == null || number.length() < 12 ) {
111 throw new IllegalArgumentException( "invalid number == " + number );
112 }
113
114 // Character indices of components in string [(]ddd[)] ddd-dddd[ {d}*]
115 number = number.trim();
116 if ( number.charAt(0) == '(' && number.charAt(4) == ')' ) {
117 ac1 = 1; // start of area code
118 xch0 = 5; // position before exchange
119 }
120 else {
121 ac1 = 0; // start of area code
122 xch0 = 3; // position before exchange
123 }
124 loc1 = xch0+5; // start of local number
125 xt0 = loc1 + 4; // position before optional extension
126
127 this.areaCode = parseAreaCode( number, ac1 );
128 this.exchange = parseExchange( number, xch0 );
129 this.local = parseLocal( number, loc1 );
130
131 this.extension = parseExtension( number, xt0 );
132 if ( this.extension != null && this.extension.trim().length() == 0 ) {
133 this.extension = null;
134 }
135
136 validateComponents();
137 if ( this.extension == null && number.length() > xt0 ) {
138 String msg = "unable to parse extension from '" + number + "'";
139 throw new IllegalArgumentException( msg );
140 }
141
142 } // ctor(String)
143
144 private void validateComponents() {
145
146 if ( !isValidAreaCode(this.areaCode) ) {
147 throw new IllegalArgumentException( "null or invalid area code" );
148 }
149
150 if ( !isValidExchange(this.exchange) ) {
151 throw new IllegalArgumentException( "null or invalid exchange" );
152 }
153
154 if ( !isValidLocal(this.local) ) {
155 throw new IllegalArgumentException( "null or invalid local number" );
156 }
157
158 } // validateComponents()
159
160 /** Creates a valid telephone number or returns null */
161 public static Telephone createTelephone( String areaCode,
162 String exchange, String local, String extension ) {
163 Telephone retVal = null;
164 if ( isValidAreaCode(areaCode)
165 && isValidExchange(exchange) && isValidLocal(local) ) {
166 retVal = new Telephone(areaCode,exchange,local,extension);
167 }
168 return retVal;
169 } // createTelephone(String,String,String,String)
170
171 public String getAreaCode() { return this.areaCode; }
172 public String getExchange() { return this.exchange; }
173 public String getLocal() { return this.local; }
174 public String getExtension() { return this.extension; }
175
176 /** @return a 3-digit number starting at index 'ac1', or null on error */
177 private static String parseAreaCode( String number, int ac1 ) {
178 String retVal = null;
179 if ( Character.isDigit( number.charAt(ac1) )
180 && Character.isDigit( number.charAt(ac1+1) )
181 && Character.isDigit( number.charAt(ac1+2) ) ) {
182 retVal = number.substring( ac1, ac1+3 );
183 }
184
185 return retVal;
186 } // parseAreaCode(String)
187
188 /** @return a 3-digit number, or null on error */
189 private static String parseExchange( String number, int xch0 ) {
190 String retVal = null;
191 if ( (number.charAt(xch0) == ' ' && number.charAt(xch0+4) == '-')
192 || (number.charAt(xch0) == '-' && number.charAt(xch0+4) == '-') ) {
193 if ( Character.isDigit( number.charAt(xch0+1) )
194 && Character.isDigit( number.charAt(xch0+2) )
195 && Character.isDigit( number.charAt(xch0+3) ) ) {
196 retVal = number.substring( xch0+1, xch0+4 );
197 }
198 }
199
200 return retVal;
201 } // parseExchange(String)
202
203 /** @return a 4-digit number, or null on error */
204 private static String parseLocal( String number, int loc1 ) {
205 String retVal = null;
206 if ( Character.isDigit( number.charAt(loc1) )
207 && Character.isDigit( number.charAt(loc1+1) )
208 && Character.isDigit( number.charAt(loc1+2) )
209 && Character.isDigit( number.charAt(loc1+3) ) ) {
210 retVal = number.substring( loc1, loc1+4 );
211 }
212
213 return retVal;
214 } // parseLocal(String)
215
216 /** @return a numerical extension or null */
217 private static String parseExtension( String number, int xt0 ) {
218 String retVal = null;
219
220 // Optional extension is " Ext. " plus at least one digit
221 if ( number.length() > xt0+6 ) {
222 String nxt = number.substring(xt0, xt0+5);
223 if ( nxt.compareTo(" ext.") == 0 || nxt.compareTo(" Ext.") == 0
224 || nxt.compareTo(" EXT.") == 0 ) {
225 try {
226 int test = Integer.parseInt( number.substring(xt0+6) );
227 retVal = number.substring(xt0+6);
228 }
229 catch( NumberFormatException ignored ) {}
230 }
231 }
232
233 return retVal;
234 } // parseExtension(String)
235
236 public String toString() {
237 StringBuffer sb = new StringBuffer(25);
238 sb.append( "(" );
239 sb.append( this.areaCode );
240 sb.append( ") " );
241 sb.append( this.exchange );
242 sb.append( "-" );
243 sb.append( this.local );
244 if ( this.extension != null && this.extension.trim().length() > 0 ) {
245 sb.append( " Ext. " );
246 sb.append( this.extension.trim() );
247 }
248 String retVal = new String( sb );
249
250 return retVal;
251 } // toString()
252
253 public boolean equals( Object o ) {
254 boolean retVal;
255 if ( o == null ) {
256 retVal = false;
257 }
258 else if ( o instanceof Telephone ) {
259 Telephone that = (Telephone) o;
260 retVal = this.areaCode.equals(that.areaCode);
261 retVal = retVal && this.exchange.equals(that.exchange);
262 retVal = retVal && this.local.equals(that.local);
263 if ( this.extension != null ) {
264 retVal = retVal && this.extension.equals(that.extension);
265 }
266 else {
267 retVal = retVal && that.extension == null;
268 }
269 }
270 else if ( o instanceof String ) {
271 retVal = false;
272 try {
273 Telephone other = new Telephone( (String)o );
274 retVal = this.equals(other);
275 } catch( Exception x ) {
276 // don't care
277 }
278 }
279 else {
280 retVal = false;
281 }
282
283 return retVal;
284 } // equals(Object)
285
286 public int hashCode() {
287 int retVal = this.local.hashCode(); // good enough
288 return retVal;
289 }
290
291 } // Telephone
292
293 /*
294 * $Log: Telephone.java,v $
295 * Revision 1.13 2003/02/26 03:38:45 rphall
296 * Added copyright and GPL license
297 *
298 * Revision 1.12 2003/02/21 15:08:33 rphall
299 * More fixes to blank extension bug
300 *
301 * Revision 1.11 2003/02/21 14:30:37 rphall
302 * Fixed bug with blank extensions
303 *
304 * Revision 1.10 2003/02/19 22:26:39 rphall
305 * Removed gratuitous use of CLRA acronym
306 *
307 * Revision 1.9 2003/02/19 03:16:56 rphall
308 * Added validation for extension
309 *
310 * Revision 1.8 2003/02/18 04:21:34 rphall
311 * Added validation methods
312 *
313 * Revision 1.7 2003/02/15 04:31:42 rphall
314 * Changes connected to major revision of MemberBean
315 *
316 * Revision 1.6 2003/02/10 05:10:51 rphall
317 * Added parsing for '-' between area code and exchange
318 *
319 */
320