This repository has been archived by the owner on Feb 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from daschJulia/25562-fix_phoneNumbers_removed_…
…numbers update PhoneNumberFormatter
- Loading branch information
Showing
9 changed files
with
196 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,18 +2,23 @@ | |
|
||
import com.google.i18n.phonenumbers.NumberParseException; | ||
import com.google.i18n.phonenumbers.PhoneNumberUtil; | ||
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType; | ||
import com.google.i18n.phonenumbers.Phonenumber; | ||
|
||
import net.contargo.types.Loggable; | ||
|
||
import org.apache.commons.lang.StringUtils; | ||
|
||
import java.util.Arrays; | ||
|
||
|
||
/** | ||
* Handles the formatting of telephone numbers. | ||
* | ||
* @author Robin Jayasinghe - [email protected] | ||
* @author Julia Dasch - [email protected] | ||
*/ | ||
public class PhoneNumberFormatter { | ||
public class PhoneNumberFormatter implements Loggable { | ||
|
||
private static PhoneNumberFormatter instance = null; | ||
|
||
|
@@ -67,22 +72,29 @@ public String parseAndFormatToE164Format(final String phoneNumber) throws PhoneN | |
public String parseAndFormatToDIN5008(final String phoneNumber) throws PhoneNumberFormattingException { | ||
|
||
// strip all whitespace and line breaks from input | ||
final String phoneNumberWithoutWhitespace = trimPhoneNumber(phoneNumber); | ||
final String trimPhoneNumber = trimPhoneNumber(phoneNumber); | ||
|
||
// pre-validation and formatting with libphonenumber | ||
final Phonenumber.PhoneNumber parsedNumber = parsePhoneNumber(phoneNumber, phoneNumberWithoutWhitespace); | ||
final Phonenumber.PhoneNumber parsedNumber = parsePhoneNumber(phoneNumber, trimPhoneNumber); | ||
final String formattedNumber = phoneNumberUtil.format(parsedNumber, | ||
PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); | ||
|
||
// extract primitives from libphonenumber parse result | ||
final String nationalNumber = String.valueOf(parsedNumber.getNationalNumber()); | ||
final int countryCode = parsedNumber.getCountryCode(); | ||
final PhoneNumberUtil.PhoneNumberType phoneNumberType = PhoneNumberUtil.getInstance() | ||
.getNumberType(parsedNumber); | ||
final PhoneNumberType phoneNumberType = getPhoneNumberType(phoneNumber); | ||
|
||
// trigger the actual formatting | ||
return formatDIN5008(countryCode, nationalNumber, formattedNumber, | ||
phoneNumberType.equals(PhoneNumberUtil.PhoneNumberType.FIXED_LINE)); | ||
String formatDIN5008 = formatDIN5008(countryCode, nationalNumber, formattedNumber, | ||
phoneNumberType.equals(PhoneNumberType.FIXED_LINE)); | ||
|
||
if (formatDIN5008.length() >= trimPhoneNumber.length()) { | ||
return formatDIN5008; | ||
} else { | ||
throw new PhoneNumberFormattingException(String.format( | ||
"can not format number. the formatted phone number('%s') has less digits than the incoming phone number('%s')", | ||
formatDIN5008, phoneNumber)); | ||
} | ||
} | ||
|
||
|
||
|
@@ -152,8 +164,7 @@ public String getNationalSignificantNumber(final String phoneNumber) throws Phon | |
* | ||
* @throws PhoneNumberFormattingException if the phoneNumber cannot be parsed | ||
*/ | ||
public PhoneNumberUtil.PhoneNumberType getPhoneNumberType(final String phoneNumber) | ||
throws PhoneNumberFormattingException { | ||
public PhoneNumberType getPhoneNumberType(final String phoneNumber) throws PhoneNumberFormattingException { | ||
|
||
Phonenumber.PhoneNumber parsedNumber = parsePhoneNumber(phoneNumber, trimPhoneNumber(phoneNumber)); | ||
|
||
|
@@ -179,34 +190,32 @@ private Phonenumber.PhoneNumber parsePhoneNumber(String phoneNumber, String phon | |
private String formatDIN5008(final int countryCode, final String nationalNumber, final String preFormattedNumber, | ||
final boolean isFixedLine) throws PhoneNumberFormattingException { | ||
|
||
final AreaCodeAndConnectionNumber areaCodeAndConnectionNumber; | ||
final NationalNumber areaCodeAndConnectionNumber; | ||
|
||
if (isFixedLine) { | ||
areaCodeAndConnectionNumber = getAreaCodeAndConnectionNumberFromFixedNumber(preFormattedNumber); | ||
areaCodeAndConnectionNumber = getNationalNumberFromFixedNumber(preFormattedNumber); | ||
} else { | ||
areaCodeAndConnectionNumber = getAreaCodeAndConnectionNumberFromMobileNumber(nationalNumber); | ||
areaCodeAndConnectionNumber = getNationalNumberFromMobileNumber(nationalNumber); | ||
} | ||
|
||
return chunkAndFormatResult(countryCode, areaCodeAndConnectionNumber); | ||
} | ||
|
||
|
||
private AreaCodeAndConnectionNumber getAreaCodeAndConnectionNumberFromMobileNumber(String nationalNumber) { | ||
|
||
AreaCodeAndConnectionNumber areaCodeAndConnectionNumber; | ||
private NationalNumber getNationalNumberFromMobileNumber(String nationalNumber) { | ||
|
||
if (nationalNumber.length() > 4) { // only for numbers longer than 4 digits | ||
areaCodeAndConnectionNumber = new AreaCodeAndConnectionNumber(nationalNumber.substring(0, 3), | ||
nationalNumber.substring(3, nationalNumber.length()).replaceAll("\\D", "")); | ||
|
||
String connectionNumber = nationalNumber.substring(3).replaceAll("\\D", ""); | ||
|
||
return new NationalNumber(nationalNumber.substring(0, 3), connectionNumber); | ||
} else { // for the shorter numbers | ||
areaCodeAndConnectionNumber = new AreaCodeAndConnectionNumber("", nationalNumber); | ||
return new NationalNumber("", nationalNumber); | ||
} | ||
|
||
return areaCodeAndConnectionNumber; | ||
} | ||
|
||
|
||
private AreaCodeAndConnectionNumber getAreaCodeAndConnectionNumberFromFixedNumber(String preFormattedNumber) | ||
private NationalNumber getNationalNumberFromFixedNumber(String preFormattedNumber) | ||
throws PhoneNumberFormattingException { | ||
|
||
// fist element should be the country code | ||
|
@@ -216,26 +225,29 @@ private AreaCodeAndConnectionNumber getAreaCodeAndConnectionNumberFromFixedNumbe | |
throw new PhoneNumberFormattingException(String.format("Could not extract anything from input number: %s", | ||
preFormattedNumber)); | ||
} else if (splittedPreformattedNumber.length <= 2) { // two elements -> take the second element as connection | ||
return new AreaCodeAndConnectionNumber("", splittedPreformattedNumber[1].replaceAll("\\D+", "")); | ||
return new NationalNumber("", splittedPreformattedNumber[1].replaceAll("\\D+", "")); | ||
} else { // more than one element -> take the second as area code and the third as connection number (first is country code) | ||
return new AreaCodeAndConnectionNumber(preFormattedNumber.split(" ")[1], | ||
splittedPreformattedNumber[2].replaceAll("\\D+", "")); | ||
|
||
String[] connectionNumbers = Arrays.copyOfRange(splittedPreformattedNumber, 2, | ||
splittedPreformattedNumber.length); | ||
String connectionNumber = StringUtils.join(connectionNumbers, ""); | ||
|
||
return new NationalNumber(splittedPreformattedNumber[1], connectionNumber.replaceAll("\\D+", "")); | ||
} | ||
} | ||
|
||
|
||
private String chunkAndFormatResult(int countryCode, AreaCodeAndConnectionNumber areaCodeAndConnectionNumber) { | ||
private String chunkAndFormatResult(int countryCode, NationalNumber nationalNumber) { | ||
|
||
logger().debug("chunk the number:'{} {} {}' into blocks of 4 digits. the last block can be 1-4 digits", | ||
countryCode, nationalNumber.areaCode, nationalNumber.connectionNumber); | ||
|
||
// chunk the number into blocks of 4 digits. the last block can be 1-4 digits | ||
final String chunkedConnectionNumber = String.join(" ", | ||
areaCodeAndConnectionNumber.connectionNumber.split("(?<=\\G.{4})")); | ||
final String chunkedConnectionNumber = String.join(" ", nationalNumber.connectionNumber.split("(?<=\\G.{4})")); | ||
|
||
if (StringUtils.isBlank(areaCodeAndConnectionNumber.areaCode)) { | ||
if (StringUtils.isBlank(nationalNumber.areaCode)) { | ||
return String.format("+%d %s", countryCode, chunkedConnectionNumber); | ||
} else { | ||
// target format | ||
return String.format("+%d %s %s", countryCode, areaCodeAndConnectionNumber.areaCode, | ||
chunkedConnectionNumber); | ||
return String.format("+%d %s %s", countryCode, nationalNumber.areaCode, chunkedConnectionNumber); | ||
} | ||
} | ||
|
||
|
@@ -249,17 +261,21 @@ private String chunkAndFormatResult(int countryCode, AreaCodeAndConnectionNumber | |
*/ | ||
private String trimPhoneNumber(String phoneNumber) { | ||
|
||
logger().debug("trim number:'{}' remove spaces, new Lines, '/', '-' and '()'.", phoneNumber); | ||
|
||
String phoneNumberWithoutNewLine = phoneNumber.replaceAll("\\s+", "").replaceAll("\n", ""); | ||
String phoneNumberWithoutBraces = phoneNumberWithoutNewLine.replaceAll("\\(", "").replaceAll("\\)", ""); | ||
|
||
return phoneNumberWithoutNewLine.replaceAll("/", "").replaceAll("-", ""); | ||
return phoneNumberWithoutBraces.replaceAll("/", "").replaceAll("-", ""); | ||
} | ||
|
||
private class AreaCodeAndConnectionNumber { | ||
// National number contains areaCode and connectionNumber | ||
private class NationalNumber { | ||
|
||
public final String areaCode; | ||
public final String connectionNumber; | ||
|
||
private AreaCodeAndConnectionNumber(String areaCode, String connectionNumber) { | ||
private NationalNumber(String areaCode, String connectionNumber) { | ||
|
||
this.areaCode = areaCode; | ||
this.connectionNumber = connectionNumber; | ||
|
8 changes: 8 additions & 0 deletions
8
src/main/java/net/contargo/types/telephony/validation/PhoneNumberValidationResult.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package net.contargo.types.telephony.validation; | ||
|
||
public enum PhoneNumberValidationResult { | ||
|
||
ZERO_NUMBER, | ||
CAN_NOT_FORMATTED, | ||
TOO_LARGE | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,9 @@ | |
|
||
import org.apache.commons.lang.StringUtils; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import javax.validation.ConstraintValidator; | ||
import javax.validation.ConstraintValidatorContext; | ||
|
||
|
@@ -14,7 +17,9 @@ | |
* @author Robin Jayasinghe - [email protected] | ||
* @author Olle Törnström - [email protected] | ||
*/ | ||
public class ValidPhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, String> { | ||
public class ValidPhoneNumberValidator implements ConstraintValidator<ValidPhoneNumber, PhoneNumber> { | ||
|
||
private static final int PHONE_NUMBER_SIZE = 64; | ||
|
||
@Override | ||
public void initialize(ValidPhoneNumber a) { | ||
|
@@ -24,21 +29,73 @@ public void initialize(ValidPhoneNumber a) { | |
|
||
|
||
@Override | ||
public boolean isValid(String value, ConstraintValidatorContext cvc) { | ||
|
||
PhoneNumber phoneNumber = new PhoneNumber(value); | ||
public boolean isValid(PhoneNumber value, ConstraintValidatorContext cvc) { | ||
|
||
// We must assume non-null, non-empty is validated elsewhere | ||
if (StringUtils.isBlank(value) || phoneNumber.getRawPhoneNumber().trim().isEmpty()) { | ||
if (value == null) { | ||
return true; | ||
} | ||
|
||
return phoneNumber.isPhoneNumber() && !isPhoneNumberOnlyZeros(phoneNumber); | ||
List<PhoneNumberValidationResult> validationResults = checkPhoneNumber(value); | ||
|
||
validationResults.forEach(validationResult -> { | ||
final String messageTemplate; | ||
String propertyName = "phone"; | ||
|
||
if (value.isMobile()) { | ||
propertyName = "mobile"; | ||
} | ||
|
||
switch (validationResult) { | ||
case ZERO_NUMBER: | ||
messageTemplate = "{ZERO_NUMBER}"; | ||
break; | ||
|
||
case CAN_NOT_FORMATTED: | ||
messageTemplate = "{CAN_NOT_FORMATTED}"; | ||
break; | ||
|
||
case TOO_LARGE: | ||
messageTemplate = "{TOO_LARGE}"; | ||
break; | ||
|
||
default: | ||
throw new IllegalArgumentException("unknown validation result for contact info"); | ||
} | ||
|
||
reportConstraintViolation(cvc, messageTemplate, propertyName); | ||
}); | ||
|
||
return validationResults.isEmpty(); | ||
} | ||
|
||
|
||
private List<PhoneNumberValidationResult> checkPhoneNumber(final PhoneNumber phoneNumber) { | ||
|
||
List<PhoneNumberValidationResult> phoneNumberValidationResults = new ArrayList<>(); | ||
|
||
if (!StringUtils.isBlank(phoneNumber.getRawPhoneNumber())) { | ||
if (phoneNumber.containsOnlyZeros()) { | ||
phoneNumberValidationResults.add(PhoneNumberValidationResult.ZERO_NUMBER); | ||
} | ||
|
||
if (!phoneNumber.canBeFormatted()) { | ||
phoneNumberValidationResults.add(PhoneNumberValidationResult.CAN_NOT_FORMATTED); | ||
} | ||
|
||
if (phoneNumber.getRawPhoneNumber().length() > PHONE_NUMBER_SIZE) { | ||
phoneNumberValidationResults.add(PhoneNumberValidationResult.TOO_LARGE); | ||
} | ||
} | ||
|
||
return phoneNumberValidationResults; | ||
} | ||
|
||
|
||
private boolean isPhoneNumberOnlyZeros(PhoneNumber phoneNumber) { | ||
private void reportConstraintViolation(final ConstraintValidatorContext context, final String messageTemplate, | ||
final String propertyName) { | ||
|
||
return phoneNumber.getRawPhoneNumber().matches("0+$"); | ||
context.buildConstraintViolationWithTemplate(messageTemplate) | ||
.addPropertyNode(propertyName).addConstraintViolation(); | ||
} | ||
} |
Oops, something went wrong.