Clover coverage report - Maven Clover report
Coverage timestamp: Sun Aug 20 2006 04:01:04 PDT
file stats: LOC: 567   Methods: 28
NCLOC: 254   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
InternetAddress.java 57.1% 57.3% 71.4% 59%
coverage coverage
 1    /**
 2    *
 3    * Copyright 2003-2004 The Apache Software Foundation
 4    *
 5    * Licensed under the Apache License, Version 2.0 (the "License");
 6    * you may not use this file except in compliance with the License.
 7    * You may obtain a copy of the License at
 8    *
 9    * http://www.apache.org/licenses/LICENSE-2.0
 10    *
 11    * Unless required by applicable law or agreed to in writing, software
 12    * distributed under the License is distributed on an "AS IS" BASIS,
 13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14    * See the License for the specific language governing permissions and
 15    * limitations under the License.
 16    */
 17    package javax.mail.internet;
 18   
 19    import java.io.UnsupportedEncodingException;
 20    import java.lang.reflect.Array;
 21    import java.net.InetAddress;
 22    import java.net.UnknownHostException;
 23    import java.util.ArrayList;
 24    import java.util.List;
 25    import java.util.StringTokenizer;
 26   
 27    import javax.mail.Address;
 28    import javax.mail.Session;
 29   
 30    import org.apache.geronimo.mail.util.SessionUtil;
 31   
 32    /**
 33    * A representation of an Internet email address as specified by RFC822 in
 34    * conjunction with a human-readable personal name that can be encoded as
 35    * specified by RFC2047.
 36    * A typical address is "user@host.domain" and personal name "Joe User"
 37    *
 38    * @version $Rev: 382387 $ $Date: 2006-03-02 06:18:24 -0800 (Thu, 02 Mar 2006) $
 39    */
 40    public class InternetAddress extends Address implements Cloneable {
 41    /**
 42    * The address in RFC822 format.
 43    */
 44    protected String address;
 45   
 46    /**
 47    * The personal name in RFC2047 format.
 48    * Subclasses must ensure that this field is updated if the personal field
 49    * is updated; alternatively, it can be invalidated by setting to null
 50    * which will cause it to be recomputed.
 51    */
 52    protected String encodedPersonal;
 53   
 54    /**
 55    * The personal name as a Java String.
 56    * Subclasses must ensure that this field is updated if the encodedPersonal field
 57    * is updated; alternatively, it can be invalidated by setting to null
 58    * which will cause it to be recomputed.
 59    */
 60    protected String personal;
 61   
 62  353 public InternetAddress() {
 63    }
 64   
 65  67 public InternetAddress(String address) throws AddressException {
 66  67 this(address, true);
 67    }
 68   
 69  107 public InternetAddress(String address, boolean strict) throws AddressException {
 70    // use the parse method to process the address. This has the wierd side effect of creating a new
 71    // InternetAddress instance to create an InternetAddress, but these are lightweight objects and
 72    // we need access to multiple pieces of data from the parsing process.
 73  107 AddressParser parser = new AddressParser(address, strict ? AddressParser.STRICT : AddressParser.NONSTRICT);
 74   
 75  107 InternetAddress parsedAddress = parser.parseAddress();
 76    // copy the important information, which right now is just the address and
 77    // personal info.
 78  107 this.address = parsedAddress.address;
 79  107 this.personal = parsedAddress.personal;
 80  107 this.encodedPersonal = parsedAddress.encodedPersonal;
 81    }
 82   
 83  0 public InternetAddress(String address, String personal) throws UnsupportedEncodingException {
 84  0 this(address, personal, null);
 85    }
 86   
 87  0 public InternetAddress(String address, String personal, String charset) throws UnsupportedEncodingException {
 88  0 this.address = address;
 89  0 setPersonal(personal, charset);
 90    }
 91   
 92    /**
 93    * Clone this object.
 94    *
 95    * @return a copy of this object as created by Object.clone()
 96    */
 97  0 public Object clone() {
 98  0 try {
 99  0 return super.clone();
 100    } catch (CloneNotSupportedException e) {
 101  0 throw new Error();
 102    }
 103    }
 104   
 105    /**
 106    * Return the type of this address.
 107    *
 108    * @return the type of this address; always "rfc822"
 109    */
 110  0 public String getType() {
 111  0 return "rfc822";
 112    }
 113   
 114    /**
 115    * Set the address.
 116    * No validation is performed; validate() can be used to check if it is valid.
 117    *
 118    * @param address the address to set
 119    */
 120  335 public void setAddress(String address) {
 121  335 this.address = address;
 122    }
 123   
 124    /**
 125    * Set the personal name.
 126    * The name is first checked to see if it can be encoded; if this fails then an
 127    * UnsupportedEncodingException is thrown and no fields are modified.
 128    *
 129    * @param name the new personal name
 130    * @param charset the charset to use; see {@link MimeUtility#encodeWord(String, String, String) MimeUtilityencodeWord}
 131    * @throws UnsupportedEncodingException if the name cannot be encoded
 132    */
 133  0 public void setPersonal(String name, String charset) throws UnsupportedEncodingException {
 134  0 personal = name;
 135  0 if (name != null) {
 136  0 encodedPersonal = MimeUtility.encodeWord(name, charset, null);
 137    }
 138    else {
 139  0 encodedPersonal = null;
 140    }
 141    }
 142   
 143    /**
 144    * Set the personal name.
 145    * The name is first checked to see if it can be encoded using {@link MimeUtility#encodeWord(String)}; if this fails then an
 146    * UnsupportedEncodingException is thrown and no fields are modified.
 147    *
 148    * @param name the new personal name
 149    * @throws UnsupportedEncodingException if the name cannot be encoded
 150    */
 151  302 public void setPersonal(String name) throws UnsupportedEncodingException {
 152  302 personal = name;
 153  302 if (name != null) {
 154  63 encodedPersonal = MimeUtility.encodeWord(name);
 155    }
 156    else {
 157  239 encodedPersonal = null;
 158    }
 159    }
 160   
 161    /**
 162    * Return the address.
 163    *
 164    * @return the address
 165    */
 166  318 public String getAddress() {
 167  318 return address;
 168    }
 169   
 170    /**
 171    * Return the personal name.
 172    * If the personal field is null, then an attempt is made to decode the encodedPersonal
 173    * field using {@link MimeUtility#decodeWord(String)}; if this is sucessful, then
 174    * the personal field is updated with that value and returned; if there is a problem
 175    * decoding the text then the raw value from encodedPersonal is returned.
 176    *
 177    * @return the personal name
 178    */
 179  210 public String getPersonal() {
 180  210 if (personal == null && encodedPersonal != null) {
 181  0 try {
 182  0 personal = MimeUtility.decodeWord(encodedPersonal);
 183    } catch (ParseException e) {
 184  0 return encodedPersonal;
 185    } catch (UnsupportedEncodingException e) {
 186  0 return encodedPersonal;
 187    }
 188    }
 189  210 return personal;
 190    }
 191   
 192    /**
 193    * Return the encoded form of the personal name.
 194    * If the encodedPersonal field is null, then an attempt is made to encode the
 195    * personal field using {@link MimeUtility#encodeWord(String)}; if this is
 196    * successful then the encodedPersonal field is updated with that value and returned;
 197    * if there is a problem encoding the text then null is returned.
 198    *
 199    * @return the encoded form of the personal name
 200    */
 201  219 private String getEncodedPersonal() {
 202  219 if (encodedPersonal == null && personal != null) {
 203  0 try {
 204  0 encodedPersonal = MimeUtility.encodeWord(personal);
 205    } catch (UnsupportedEncodingException e) {
 206    // as we could not encode this, return null
 207  0 return null;
 208    }
 209    }
 210  219 return encodedPersonal;
 211    }
 212   
 213   
 214    /**
 215    * Return a string representation of this address using only US-ASCII characters.
 216    *
 217    * @return a string representation of this address
 218    */
 219  235 public String toString() {
 220    // group addresses are always returned without modification.
 221  235 if (isGroup()) {
 222  16 return address;
 223    }
 224   
 225    // if we have personal information, then we need to return this in the route-addr form:
 226    // "personal <address>". If there is no personal information, then we typically return
 227    // the address without the angle brackets. However, if the address contains anything other
 228    // than atoms, '@', and '.' (e.g., uses domain literals, has specified routes, or uses
 229    // quoted strings in the local-part), we bracket the address.
 230  219 String p = getEncodedPersonal();
 231  219 if (p == null) {
 232  156 return formatAddress(address);
 233    }
 234    else {
 235  63 StringBuffer buf = new StringBuffer(p.length() + 8 + address.length() + 3);
 236  63 buf.append(AddressParser.quoteString(p));
 237  63 buf.append(" <").append(address).append(">");
 238  63 return buf.toString();
 239    }
 240    }
 241   
 242    /**
 243    * Check the form of an address, and enclose it within brackets
 244    * if they are required for this address form.
 245    *
 246    * @param a The source address.
 247    *
 248    * @return A formatted address, which can be the original address string.
 249    */
 250  174 private String formatAddress(String a)
 251    {
 252    // this could be a group address....we don't muck with those.
 253  174 if (address.endsWith(";") && address.indexOf(":") > 0) {
 254  1 return address;
 255    }
 256   
 257  173 if (AddressParser.containsCharacters(a, "()<>,;:\"[]")) {
 258  62 StringBuffer buf = new StringBuffer(address.length() + 3);
 259  62 buf.append("<").append(address).append(">");
 260  62 return buf.toString();
 261    }
 262  111 return address;
 263    }
 264   
 265    /**
 266    * Return a string representation of this address using Unicode characters.
 267    *
 268    * @return a string representation of this address
 269    */
 270  0 public String toUnicodeString() {
 271    // group addresses are always returned without modification.
 272  0 if (isGroup()) {
 273  0 return address;
 274    }
 275   
 276    // if we have personal information, then we need to return this in the route-addr form:
 277    // "personal <address>". If there is no personal information, then we typically return
 278    // the address without the angle brackets. However, if the address contains anything other
 279    // than atoms, '@', and '.' (e.g., uses domain literals, has specified routes, or uses
 280    // quoted strings in the local-part), we bracket the address.
 281   
 282    // NB: The difference between toString() and toUnicodeString() is the use of getPersonal()
 283    // vs. getEncodedPersonal() for the personal portion. If the personal information contains only
 284    // ASCII-7 characters, these are the same.
 285  0 String p = getPersonal();
 286  0 if (p == null) {
 287  0 return formatAddress(address);
 288    }
 289    else {
 290  0 StringBuffer buf = new StringBuffer(p.length() + 8 + address.length() + 3);
 291  0 buf.append(AddressParser.quoteString(p));
 292  0 buf.append(" <").append(address).append(">");
 293  0 return buf.toString();
 294    }
 295    }
 296   
 297    /**
 298    * Compares two addresses for equality.
 299    * We define this as true if the other object is an InternetAddress
 300    * and the two values returned by getAddress() are equal in a
 301    * case-insensitive comparison.
 302    *
 303    * @param o the other object
 304    * @return true if the addresses are the same
 305    */
 306  53 public boolean equals(Object o) {
 307  0 if (this == o) return true;
 308  0 if (!(o instanceof InternetAddress)) return false;
 309   
 310  53 InternetAddress other = (InternetAddress) o;
 311  53 String myAddress = getAddress();
 312  53 return myAddress == null ? (other.getAddress() == null) : myAddress.equalsIgnoreCase(other.getAddress());
 313    }
 314   
 315    /**
 316    * Return the hashCode for this address.
 317    * We define this to be the hashCode of the address after conversion to lowercase.
 318    *
 319    * @return a hashCode for this address
 320    */
 321  0 public int hashCode() {
 322  0 return (address == null) ? 0 : address.toLowerCase().hashCode();
 323    }
 324   
 325    /**
 326    * Return true is this address is an RFC822 group address in the format
 327    * <code>phrase ":" [#mailbox] ";"</code>.
 328    * We check this by using the presense of a ':' character in the address, and a
 329    * ';' as the very last character.
 330    *
 331    * @return true is this address represents a group
 332    */
 333  445 public boolean isGroup() {
 334  445 if (address == null) {
 335  0 return false;
 336    }
 337   
 338  445 return address.endsWith(";") && address.indexOf(":") > 0;
 339    }
 340   
 341    /**
 342    * Return the members of a group address.
 343    *
 344    * If strict is true and the address does not contain an initial phrase then an AddressException is thrown.
 345    * Otherwise the phrase is skipped and the remainder of the address is checked to see if it is a group.
 346    * If it is, the content and strict flag are passed to parseHeader to extract the list of addresses;
 347    * if it is not a group then null is returned.
 348    *
 349    * @param strict whether strict RFC822 checking should be performed
 350    * @return an array of InternetAddress objects for the group members, or null if this address is not a group
 351    * @throws AddressException if there was a problem parsing the header
 352    */
 353  14 public InternetAddress[] getGroup(boolean strict) throws AddressException {
 354  14 if (address == null) {
 355  0 return null;
 356    }
 357   
 358    // create an address parser and use it to extract the group information.
 359  14 AddressParser parser = new AddressParser(address, strict ? AddressParser.STRICT : AddressParser.NONSTRICT);
 360  14 return parser.extractGroupList();
 361    }
 362   
 363    /**
 364    * Return an InternetAddress representing the current user.
 365    * <P/>
 366    * If session is not null, we first look for an address specified in its
 367    * "mail.from" property; if this is not set, we look at its "mail.user"
 368    * and "mail.host" properties and if both are not null then an address of
 369    * the form "${mail.user}@${mail.host}" is created.
 370    * If this fails to give an address, then an attempt is made to create
 371    * an address by combining the value of the "user.name" System property
 372    * with the value returned from InetAddress.getLocalHost().getHostName().
 373    * Any SecurityException raised accessing the system property or any
 374    * UnknownHostException raised getting the hostname are ignored.
 375    * <P/>
 376    * Finally, an attempt is made to convert the value obtained above to
 377    * an InternetAddress. If this fails, then null is returned.
 378    *
 379    * @param session used to obtain mail properties
 380    * @return an InternetAddress for the current user, or null if it cannot be determined
 381    */
 382  9 public static InternetAddress getLocalAddress(Session session) {
 383  9 String host = null;
 384  9 String user = null;
 385   
 386    // ok, we have several steps for resolving this. To start with, we could have a from address
 387    // configured already, which will be a full InternetAddress string. If we don't have that, then
 388    // we need to resolve a user and host to compose an address from.
 389  9 if (session != null) {
 390  7 String address = session.getProperty("mail.from");
 391    // if we got this, we can skip out now
 392  7 if (address != null) {
 393  1 try {
 394  1 return new InternetAddress(address);
 395    } catch (AddressException e) {
 396    // invalid address on the from...treat this as an error and return null.
 397  0 return null;
 398    }
 399    }
 400   
 401    // now try for user and host information. We have both session and system properties to check here.
 402    // we'll just handle the session ones here, and check the system ones below if we're missing information.
 403  6 user = session.getProperty("mail.user");
 404  6 host = session.getProperty("mail.host");
 405    }
 406   
 407  8 try {
 408   
 409    // if either user or host is null, then we check non-session sources for the information.
 410  8 if (user == null) {
 411  4 user = System.getProperty("user.name");
 412    }
 413   
 414  8 if (host == null) {
 415  4 host = InetAddress.getLocalHost().getHostName();
 416    }
 417   
 418  8 if (user != null && host != null) {
 419    // if we have both a user and host, we can create a local address
 420  7 return new InternetAddress(user + '@' + host);
 421    }
 422    } catch (AddressException e) {
 423    // ignore
 424    } catch (UnknownHostException e) {
 425    // ignore
 426    } catch (SecurityException e) {
 427    // ignore
 428    }
 429  1 return null;
 430    }
 431   
 432    /**
 433    * Convert the supplied addresses into a single String of comma-separated text as
 434    * produced by {@link InternetAddress#toString() toString()}.
 435    * No line-break detection is performed.
 436    *
 437    * @param addresses the array of addresses to convert
 438    * @return a one-line String of comma-separated addresses
 439    */
 440  8 public static String toString(Address[] addresses) {
 441  8 if (addresses == null || addresses.length == 0) {
 442  0 return null;
 443    }
 444  8 if (addresses.length == 1) {
 445  7 return addresses[0].toString();
 446    } else {
 447  1 StringBuffer buf = new StringBuffer(addresses.length * 32);
 448  1 buf.append(addresses[0].toString());
 449  1 for (int i = 1; i < addresses.length; i++) {
 450  1 buf.append(", ");
 451  1 buf.append(addresses[i].toString());
 452    }
 453  1 return buf.toString();
 454    }
 455    }
 456   
 457    /**
 458    * Convert the supplies addresses into a String of comma-separated text,
 459    * inserting line-breaks between addresses as needed to restrict the line
 460    * length to 72 characters. Splits will only be introduced between addresses
 461    * so an address longer than 71 characters will still be placed on a single
 462    * line.
 463    *
 464    * @param addresses the array of addresses to convert
 465    * @param used the starting column
 466    * @return a String of comma-separated addresses with optional line breaks
 467    */
 468  0 public static String toString(Address[] addresses, int used) {
 469  0 if (addresses == null || addresses.length == 0) {
 470  0 return null;
 471    }
 472  0 if (addresses.length == 1) {
 473  0 String s = addresses[0].toString();
 474  0 if (used + s.length() > 72) {
 475  0 s = "\r\n " + s;
 476    }
 477  0 return s;
 478    } else {
 479  0 StringBuffer buf = new StringBuffer(addresses.length * 32);
 480  0 for (int i = 0; i < addresses.length; i++) {
 481  0 String s = addresses[1].toString();
 482  0 if (i == 0) {
 483  0 if (used + s.length() + 1 > 72) {
 484  0 buf.append("\r\n ");
 485  0 used = 2;
 486    }
 487    } else {
 488  0 if (used + s.length() + 1 > 72) {
 489  0 buf.append(",\r\n ");
 490  0 used = 2;
 491    } else {
 492  0 buf.append(", ");
 493  0 used += 2;
 494    }
 495    }
 496  0 buf.append(s);
 497  0 used += s.length();
 498    }
 499  0 return buf.toString();
 500    }
 501    }
 502   
 503    /**
 504    * Parse addresses out of the string with basic checking.
 505    *
 506    * @param addresses the addresses to parse
 507    * @return an array of InternetAddresses parsed from the string
 508    * @throws AddressException if addresses checking fails
 509    */
 510  20 public static InternetAddress[] parse(String addresses) throws AddressException {
 511  20 return parse(addresses, true);
 512    }
 513   
 514    /**
 515    * Parse addresses out of the string.
 516    *
 517    * @param addresses the addresses to parse
 518    * @param strict if true perform detailed checking, if false just perform basic checking
 519    * @return an array of InternetAddresses parsed from the string
 520    * @throws AddressException if address checking fails
 521    */
 522  60 public static InternetAddress[] parse(String addresses, boolean strict) throws AddressException {
 523  60 return parse(addresses, strict ? AddressParser.STRICT : AddressParser.NONSTRICT);
 524    }
 525   
 526    /**
 527    * Parse addresses out of the string.
 528    *
 529    * @param addresses the addresses to parse
 530    * @param strict if true perform detailed checking, if false perform little checking
 531    * @return an array of InternetAddresses parsed from the string
 532    * @throws AddressException if address checking fails
 533    */
 534  127 public static InternetAddress[] parseHeader(String addresses, boolean strict) throws AddressException {
 535  127 return parse(addresses, strict ? AddressParser.STRICT : AddressParser.PARSE_HEADER);
 536    }
 537   
 538    /**
 539    * Parse addresses with increasing degrees of RFC822 compliance checking.
 540    *
 541    * @param addresses the string to parse
 542    * @param level The required strictness level.
 543    *
 544    * @return an array of InternetAddresses parsed from the string
 545    * @throws AddressException
 546    * if address checking fails
 547    */
 548  187 private static InternetAddress[] parse(String addresses, int level) throws AddressException {
 549    // create a parser and have it extract the list using the requested strictness leve.
 550  187 AddressParser parser = new AddressParser(addresses, level);
 551  187 return parser.parseAddressList();
 552    }
 553   
 554    /**
 555    * Validate the address portion of an internet address to ensure
 556    * validity. Throws an AddressException if any validity
 557    * problems are encountered.
 558    *
 559    * @exception AddressException
 560    */
 561  18 public void validate() throws AddressException {
 562   
 563    // create a parser using the strictest validation level.
 564  18 AddressParser parser = new AddressParser(formatAddress(address), AddressParser.STRICT);
 565  18 parser.validateAddress();
 566    }
 567    }