All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.knightsoftnet.validators.shared.util.AbstractPhoneNumberUtil Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package de.knightsoftnet.validators.shared.util;

import de.knightsoftnet.validators.shared.data.CountryEnum;
import de.knightsoftnet.validators.shared.data.PhoneAreaCodeData;
import de.knightsoftnet.validators.shared.data.PhoneCountryCodeData;
import de.knightsoftnet.validators.shared.data.PhoneCountryData;
import de.knightsoftnet.validators.shared.data.PhoneNumberData;
import de.knightsoftnet.validators.shared.data.PhoneNumberExtendedInterface;
import de.knightsoftnet.validators.shared.data.PhoneNumberInterface;
import de.knightsoftnet.validators.shared.data.ValidationInterface;
import de.knightsoftnet.validators.shared.data.ValueWithPos;

import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
 * Abstract Phone Number Util, format and parse phone numbers.
 *
 * @author Manfred Tremmel
 *
 */
@SuppressWarnings("CPD-START")
public abstract class AbstractPhoneNumberUtil {
  private static final String EXTENSION_SEPARATOR = "-";

  protected final PhoneCountryConstantsProvider phoneCountryConstantsProvider;

  protected PhoneCountryData defaultCountryData;

  protected AbstractPhoneNumberUtil(
      final PhoneCountryConstantsProvider phoneCountryConstantsProvider) {
    this(null, phoneCountryConstantsProvider);
  }

  /**
   * constructor with default country.
   *
   * @param countryCode iso code of country
   */
  protected AbstractPhoneNumberUtil(final String countryCode,
      final PhoneCountryConstantsProvider honeCountryConstantsProvider) {
    super();
    phoneCountryConstantsProvider = honeCountryConstantsProvider;
    this.setCountryCode(countryCode, Locale.ROOT);
  }

  /**
   * set country code.
   *
   * @param countryCode iso code of country
   */
  public final void setCountryCode(final String countryCode) {
    this.setCountryCode(countryCode, Locale.ROOT);
  }

  /**
   * set country code.
   *
   * @param countryCode iso code of country
   * @param locale locale to read properties in the correct language
   */
  protected final void setCountryCode(final String countryCode, final Locale locale) {
    if (StringUtils.isEmpty(countryCode)) {
      defaultCountryData = null;
    } else {
      defaultCountryData = phoneCountryConstantsProvider.getPhoneCountryConstants(locale)
          .getCountriesMap().get(countryCode);
    }
  }

  /**
   * parse phone number.
   *
   * @param phoneNumber phone number as string
   * @return PhoneNumberData
   */
  public PhoneNumberData parsePhoneNumber(final String phoneNumber) {
    return (PhoneNumberData) this.parsePhoneNumber(phoneNumber, new PhoneNumberData(),
        defaultCountryData);
  }

  /**
   * parse phone number.
   *
   * @param phoneNumber phone number as string with length
   * @return PhoneNumberData with length
   */
  public ValueWithPos parsePhoneNumber(final ValueWithPos phoneNumber) {
    return this.parsePhoneNumber(phoneNumber, new PhoneNumberData(), defaultCountryData);
  }

  /**
   * parse phone number.
   *
   * @param phoneNumber phone number as string
   * @param countryCode iso code of country
   * @return PhoneNumberData
   */
  public PhoneNumberData parsePhoneNumber(final String phoneNumber, final String countryCode) {
    return this.parsePhoneNumber(phoneNumber, countryCode, Locale.ROOT);
  }

  /**
   * parse phone number.
   *
   * @param phoneNumber phone number as string
   * @param countryCode iso code of country
   * @param locale locale to read properties in the correct language
   * @return PhoneNumberData
   */
  public PhoneNumberData parsePhoneNumber(final String phoneNumber, final String countryCode,
      final Locale locale) {
    return (PhoneNumberData) this.parsePhoneNumber(phoneNumber, new PhoneNumberData(),
        phoneCountryConstantsProvider.getPhoneCountryConstants(locale).getCountriesMap()
            .get(StringUtils.defaultString(countryCode)));
  }

  /**
   * parse phone number.
   *
   * @param phoneNumber phone number as string
   * @param countryCode iso code of country
   * @return PhoneNumberData
   */
  public ValueWithPos parsePhoneNumber(final ValueWithPos phoneNumber,
      final String countryCode) {
    return this.parsePhoneNumber(phoneNumber, countryCode, Locale.ROOT);
  }

  /**
   * parse phone number.
   *
   * @param phoneNumber phone number as string
   * @param countryCode iso code of country
   * @param locale locale to read properties in the correct language
   * @return PhoneNumberData
   */
  public ValueWithPos parsePhoneNumber(final ValueWithPos phoneNumber,
      final String countryCode, final Locale locale) {
    return this.parsePhoneNumber(phoneNumber, new PhoneNumberData(),
        phoneCountryConstantsProvider.getPhoneCountryConstants(locale).getCountriesMap()
            .get(StringUtils.defaultString(countryCode)));
  }


  /**
   * parse phone number.
   *
   * @param phoneNumber phone number as string
   * @param phoneNumberData phone number data to fill
   * @return PhoneNumberData, the same as in second parameter
   */
  public PhoneNumberInterface parsePhoneNumber(final String phoneNumber,
      final PhoneNumberInterface phoneNumberData) {
    return this.parsePhoneNumber(phoneNumber, phoneNumberData, defaultCountryData);
  }


  /**
   * parse phone number.
   *
   * @param phoneNumber phone number as string
   * @param phoneNumberData phone number data to fill
   * @param countryData country data
   * @return PhoneNumberData, the same as in second parameter
   */
  public PhoneNumberInterface parsePhoneNumber(final String phoneNumber,
      final PhoneNumberInterface phoneNumberData, final PhoneCountryData countryData) {
    if (phoneNumber == null) {
      return null;
    }
    final ValueWithPos formatedValue =
        this.parsePhoneNumber(new ValueWithPos<>(phoneNumber, -1), phoneNumberData, countryData);
    return formatedValue.getValue();
  }


  /**
   * parse phone number.
   *
   * @param phoneNumber phone number as string
   * @param phoneNumberData phone number data to fill
   * @param countryData country data
   * @return PhoneNumberData, the same as in second parameter
   */
  public ValueWithPos parsePhoneNumber(final ValueWithPos phoneNumber,
      final PhoneNumberInterface phoneNumberData, final PhoneCountryData countryData) {
    if (phoneNumber == null || phoneNumberData == null) {
      return null;
    }
    int cursorpos = phoneNumber.getPos();
    int cursorpossub = 0;
    for (int pos = 0; pos < cursorpos && pos < StringUtils.length(phoneNumber.getValue()); pos++) {
      final char character = phoneNumber.getValue().charAt(pos);
      if (character < '0' || character > '9') {
        cursorpossub++;
      }
    }
    cursorpos -= cursorpossub;
    boolean needsAreaCode = false;
    int minLength = 2;
    int maxLength = 15;
    phoneNumberData.setCountryCode(null);
    phoneNumberData.setAreaCode(null);
    phoneNumberData.setLineNumber(null);
    phoneNumberData.setExtension(null);
    final StringBuilder cleanupString = new StringBuilder(phoneNumber.getValue().length());
    final boolean containsMinus = StringUtils.contains(phoneNumber.getValue(), '-');
    boolean hasSeperator = false;
    for (final char character : StringUtils.reverse(phoneNumber.getValue()).toCharArray()) {
      switch (character) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          cleanupString.append(character);
          break;
        case '-':
          if (!hasSeperator) {
            cleanupString.append(character);
            hasSeperator = true;
          }
          break;
        case ' ':
          if (!hasSeperator && !containsMinus && cleanupString.length() <= 5) {
            cleanupString.append('-');
            hasSeperator = true;
          }
          break;
        default:
          // ignore all other characters
          break;
      }
    }
    String phoneNumberWork = StringUtils.reverse(cleanupString.toString());
    if (countryData != null) {
      if (StringUtils.isNotEmpty(countryData.getExitCode())
          && phoneNumberWork.startsWith(countryData.getExitCode())) {
        phoneNumberWork = phoneNumberWork.substring(countryData.getExitCode().length());
        cursorpos -= countryData.getExitCode().length();
      } else if (StringUtils.isNotEmpty(countryData.getTrunkCode())
          && phoneNumberWork.startsWith(countryData.getTrunkCode())) {
        phoneNumberWork = countryData.getCountryCodeData().getCountryCode()
            + phoneNumberWork.substring(countryData.getTrunkCode().length());
        if (cursorpos >= countryData.getTrunkCode().length()) {
          cursorpos -= countryData.getTrunkCode().length();
          cursorpos += StringUtils.length(countryData.getCountryCodeData().getCountryCode());
        }
      }
    }
    for (final PhoneCountryCodeData countryCode : phoneCountryConstantsProvider
        .getPhoneCountryConstants().getCountryCode()) {
      if (phoneNumberWork.startsWith(countryCode.getCountryCode())) {
        phoneNumberData.setCountryCode(countryCode.getCountryCode());
        maxLength -= StringUtils.length(countryCode.getCountryCode());
        if (phoneNumberData instanceof PhoneNumberExtendedInterface) {
          ((PhoneNumberExtendedInterface) phoneNumberData)
              .setCountryName(countryCode.getCountryCodeName());
        }
        phoneNumberWork = phoneNumberWork.substring(countryCode.getCountryCode().length());
        if (phoneNumberWork.startsWith(EXTENSION_SEPARATOR)) {
          phoneNumberWork = phoneNumberWork.substring(1);
        }
        if (countryCode.getPhoneCountryData() != null) {
          needsAreaCode = countryCode.getPhoneCountryData().isAreaCodeMustBeFilled();
          if (StringUtils.isNotEmpty(countryCode.getPhoneCountryData().getTrunkCode())
              && phoneNumberWork.startsWith(countryCode.getPhoneCountryData().getTrunkCode())) {
            phoneNumberWork = phoneNumberWork
                .substring(countryCode.getPhoneCountryData().getTrunkCode().length());
            if (cursorpos >= countryCode.getPhoneCountryData().getTrunkCode().length()) {
              cursorpos -= countryCode.getPhoneCountryData().getTrunkCode().length();
            }
          }
        }
        for (final PhoneAreaCodeData areaCode : countryCode.getAreaCodeData()) {
          if (areaCode.isRegEx() && phoneNumberWork.matches("^" + areaCode.getAreaCode() + ".*")) {
            final String areaCodeRemember = phoneNumberWork;
            phoneNumberWork =
                phoneNumberWork.replaceFirst(areaCode.getAreaCode(), StringUtils.EMPTY);
            phoneNumberData.setAreaCode(areaCodeRemember.substring(0,
                areaCodeRemember.length() - phoneNumberWork.length()));
            if (phoneNumberData instanceof PhoneNumberExtendedInterface) {
              ((PhoneNumberExtendedInterface) phoneNumberData).setAreaName(areaCode.getAreaName());
            }
            minLength = areaCode.getMinLength();
            maxLength = areaCode.getMaxLength();
            break;
          } else if (!areaCode.isRegEx() && phoneNumberWork.startsWith(areaCode.getAreaCode())) {
            phoneNumberData.setAreaCode(areaCode.getAreaCode());
            if (phoneNumberData instanceof PhoneNumberExtendedInterface) {
              ((PhoneNumberExtendedInterface) phoneNumberData).setAreaName(areaCode.getAreaName());
            }
            phoneNumberWork = phoneNumberWork.substring(areaCode.getAreaCode().length());
            minLength = areaCode.getMinLength();
            maxLength = areaCode.getMaxLength();
            break;
          }
        }

        if (phoneNumberWork.startsWith(EXTENSION_SEPARATOR)) {
          phoneNumberWork = phoneNumberWork.substring(1);
        }
        if (phoneNumberWork.contains(EXTENSION_SEPARATOR)) {
          final String[] splitedPhoneNumber = phoneNumberWork.split(EXTENSION_SEPARATOR);
          phoneNumberData.setLineNumber(splitedPhoneNumber[0]);
          if (splitedPhoneNumber.length > 1) {
            phoneNumberData.setExtension(splitedPhoneNumber[1]);
          }
        } else {
          phoneNumberData.setLineNumber(phoneNumberWork);
        }
        break;
      }
    }
    if (phoneNumberData instanceof ValidationInterface
        && phoneCountryConstantsProvider.hasPhoneCountryConstants()) {
      int callNummerLength = StringUtils.length(phoneNumberData.getLineNumber());
      int completeNumberLength = callNummerLength;
      if (StringUtils.isNotEmpty(phoneNumberData.getExtension())) {
        // if we do have extensions, phone number including extension may be longer then allowed
        // number, but at least one digit counts
        callNummerLength++;
        completeNumberLength += StringUtils.length(phoneNumberData.getExtension());
      }
      ((ValidationInterface) phoneNumberData)
          .setValid(StringUtils.isNotEmpty(phoneNumberData.getCountryCode())
              && StringUtils.isNotEmpty(phoneNumberData.getLineNumber())
              && (StringUtils.isNotEmpty(phoneNumberData.getAreaCode()) || !needsAreaCode)
              && (callNummerLength >= minLength && callNummerLength <= maxLength
                  || completeNumberLength >= minLength && completeNumberLength <= maxLength));
    }
    if (cursorpos < 0) {
      cursorpos = 0;
    } else {
      final int calculatedlength = StringUtils.length(phoneNumberData.getCountryCode())
          + StringUtils.length(phoneNumberData.getAreaCode())
          + StringUtils.length(phoneNumberData.getLineNumber())
          + StringUtils.length(phoneNumberData.getExtension());
      if (cursorpos > calculatedlength) {
        cursorpos = calculatedlength;
      }
    }
    return new ValueWithPos<>(new PhoneNumberData(phoneNumberData), cursorpos);
  }

  /**
   * format phone number in E123 format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatE123(final String phoneNumber) {
    return this.formatE123(this.parsePhoneNumber(phoneNumber), defaultCountryData);
  }

  /**
   * format phone number in E123 format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatE123(final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(
        this.formatE123WithPos(this.parsePhoneNumber(phoneNumber), defaultCountryData),
        phoneNumber);
  }

  /**
   * format phone number in E123 format.
   *
   * @param phoneNumber phone number as String to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatE123(final String phoneNumber, final String countryCode) {
    return this.formatE123(this.parsePhoneNumber(phoneNumber), phoneCountryConstantsProvider
        .getPhoneCountryConstants().getCountriesMap().get(StringUtils.defaultString(countryCode)));
  }

  /**
   * format phone number in E123 format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatE123(final PhoneNumberInterface phoneNumberData) {
    return this.formatE123(phoneNumberData, defaultCountryData);
  }

  /**
   * format phone number in E123 format.
   *
   * @param phoneNumberData phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatE123(final PhoneNumberInterface phoneNumberData,
      final String countryCode) {
    return this.formatE123(phoneNumberData, phoneCountryConstantsProvider.getPhoneCountryConstants()
        .getCountriesMap().get(StringUtils.defaultString(countryCode)));
  }

  /**
   * format phone number in E123 format.
   *
   * @param phoneNumberData phone number to format
   * @param countryData country data
   * @return formated phone number as String
   */
  public final String formatE123(final PhoneNumberInterface phoneNumberData,
      final PhoneCountryData countryData) {
    if (phoneNumberData != null && countryData != null && StringUtils.equals(
        countryData.getCountryCodeData().getCountryCode(), phoneNumberData.getCountryCode())) {
      return this.formatE123National(phoneNumberData);
    } else {
      return this.formatE123International(phoneNumberData);
    }
  }

  /**
   * format phone number in E123 format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatE123WithPos(final ValueWithPos phoneNumber,
      final String countryCode) {
    return valueWithPosDefaults(
        this.formatE123WithPos(this.parsePhoneNumber(phoneNumber, countryCode),
            phoneCountryConstantsProvider.getPhoneCountryConstants().getCountriesMap()
                .get(StringUtils.defaultString(countryCode))),
        phoneNumber);
  }

  /**
   * format phone number in E123 format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @param countryData country data
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatE123WithPos(
      final ValueWithPos phoneNumberData, final PhoneCountryData countryData) {
    if (phoneNumberData != null && countryData != null
        && StringUtils.equals(countryData.getCountryCodeData().getCountryCode(),
            phoneNumberData.getValue().getCountryCode())) {
      return this.formatE123NationalWithPos(phoneNumberData);
    } else {
      return this.formatE123InternationalWithPos(phoneNumberData);
    }
  }

  /**
   * format phone number in E123 international format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatE123International(final String phoneNumber) {
    return this.formatE123International(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in E123 international format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatE123International(final String phoneNumber, final String countryCode) {
    return this.formatE123International(this.parsePhoneNumber(phoneNumber, countryCode));
  }

  /**
   * format phone number in E123 international format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatE123International(
      final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(
        this.formatE123InternationalWithPos(this.parsePhoneNumber(phoneNumber)), phoneNumber);
  }

  /**
   * format phone number in E123 international format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatE123International(final PhoneNumberInterface phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      resultNumber.append('+').append(phoneNumberData.getCountryCode()).append(' ');
      if (StringUtils.isNotBlank(phoneNumberData.getAreaCode())) {
        resultNumber.append(phoneNumberData.getAreaCode()).append(' ');
      }
      resultNumber.append(phoneNumberData.getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getExtension())) {
        resultNumber.append(phoneNumberData.getExtension());
      }
    }
    return StringUtils.trimToNull(resultNumber.toString());
  }

  /**
   * format phone number in E123 international format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatE123InternationalWithPos(
      final ValueWithPos phoneNumber, final String countryCode) {
    return valueWithPosDefaults(
        this.formatE123InternationalWithPos(this.parsePhoneNumber(phoneNumber, countryCode)),
        phoneNumber);
  }

  /**
   * format phone number in E123 international format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatE123InternationalWithPos(
      final ValueWithPos phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (phoneNumberData == null) {
      return null;
    }
    final int cursor =
        formatE123orDin5008InternationalWithPosWithoutExtension(phoneNumberData, resultNumber);
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())
        && StringUtils.isNotBlank(phoneNumberData.getValue().getExtension())) {
      resultNumber.append(phoneNumberData.getValue().getExtension());
    }
    return new ValueWithPos<>(StringUtils.trimToNull(resultNumber.toString()), cursor);
  }

  private int formatE123orDin5008InternationalWithPosWithoutExtension(
      final ValueWithPos phoneNumberData, final StringBuilder resultNumber) {
    int cursor = phoneNumberData.getPos();
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())) {
      cursor++;
      resultNumber.append('+').append(phoneNumberData.getValue().getCountryCode());
      if (resultNumber.length() <= cursor) {
        cursor++;
      }
      resultNumber.append(' ');
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getAreaCode())) {
        resultNumber.append(phoneNumberData.getValue().getAreaCode());
        if (resultNumber.length() <= cursor) {
          cursor++;
        }
        resultNumber.append(' ');
      }
      resultNumber.append(phoneNumberData.getValue().getLineNumber());
    }
    return cursor;
  }

  /**
   * format phone number in E123 national format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatE123National(final String phoneNumber) {
    return this.formatE123National(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in E123 national format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatE123National(final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(this.formatE123NationalWithPos(this.parsePhoneNumber(phoneNumber)),
        phoneNumber);
  }

  /**
   * format phone number in E123 national format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatE123National(final String phoneNumber, final String countryCode) {
    return this.formatE123National(this.parsePhoneNumber(phoneNumber, countryCode));
  }

  /**
   * format phone number in E123 national format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatE123National(final PhoneNumberInterface phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      PhoneCountryData phoneCountryData = null;
      for (final PhoneCountryCodeData country : phoneCountryConstantsProvider
          .getPhoneCountryConstants().getCountryCode()) {
        if (StringUtils.equals(country.getCountryCode(), phoneNumberData.getCountryCode())) {
          phoneCountryData = country.getPhoneCountryData();
          break;
        }
      }
      if (phoneCountryData == null) {
        return this.formatE123International(phoneNumberData);
      }
      resultNumber.append('(').append(phoneCountryData.getTrunkCode());
      if (StringUtils.isNotBlank(phoneNumberData.getAreaCode())) {
        resultNumber.append(phoneNumberData.getAreaCode());
      }
      resultNumber.append(") ");
      resultNumber.append(phoneNumberData.getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getExtension())) {
        resultNumber.append(' ');
        resultNumber.append(phoneNumberData.getExtension());
      }
    }
    return StringUtils.trimToNull(resultNumber.toString());
  }

  /**
   * format phone number in E123 national format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatE123NationalWithPos(
      final ValueWithPos phoneNumber, final String countryCode) {
    return valueWithPosDefaults(
        this.formatE123NationalWithPos(this.parsePhoneNumber(phoneNumber, countryCode)),
        phoneNumber);
  }

  /**
   * format phone number in E123 national format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatE123NationalWithPos(
      final ValueWithPos phoneNumberData) {
    if (phoneNumberData == null) {
      return null;
    }
    int cursor = phoneNumberData.getPos();
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())) {
      PhoneCountryData phoneCountryData = null;
      for (final PhoneCountryCodeData country : phoneCountryConstantsProvider
          .getPhoneCountryConstants().getCountryCode()) {
        if (StringUtils.equals(country.getCountryCode(),
            phoneNumberData.getValue().getCountryCode())) {
          phoneCountryData = country.getPhoneCountryData();
          break;
        }
      }
      if (phoneCountryData == null) {
        return this.formatE123InternationalWithPos(phoneNumberData);
      }
      if (cursor > 0) {
        cursor -= StringUtils.length(phoneNumberData.getValue().getCountryCode());
        cursor += StringUtils.length(phoneCountryData.getTrunkCode());
      }
      cursor++;
      resultNumber.append('(').append(phoneCountryData.getTrunkCode());
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getAreaCode())) {
        resultNumber.append(phoneNumberData.getValue().getAreaCode());
      }
      if (resultNumber.length() <= cursor) {
        cursor += 2;
      }
      resultNumber.append(") ");
      resultNumber.append(phoneNumberData.getValue().getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getExtension())) {
        if (resultNumber.length() <= cursor) {
          cursor++;
        }
        resultNumber.append(' ');
        resultNumber.append(phoneNumberData.getValue().getExtension());
      }
    }
    return new ValueWithPos<>(StringUtils.trimToNull(resultNumber.toString()), cursor);
  }

  /**
   * format phone number in DIN 5008 format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatDin5008(final String phoneNumber) {
    return this.formatDin5008(this.parsePhoneNumber(phoneNumber), defaultCountryData);
  }

  /**
   * format phone number in DIN 5008 format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatDin5008(final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(
        this.formatDin5008WithPos(this.parsePhoneNumber(phoneNumber), defaultCountryData),
        phoneNumber);
  }

  /**
   * format phone number in DIN 5008 format.
   *
   * @param phoneNumber phone number as String to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatDin5008(final String phoneNumber, final String countryCode) {
    return this.formatDin5008(this.parsePhoneNumber(phoneNumber), phoneCountryConstantsProvider
        .getPhoneCountryConstants().getCountriesMap().get(StringUtils.defaultString(countryCode)));
  }

  /**
   * format phone number in DIN 5008 format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatDin5008(final PhoneNumberInterface phoneNumberData) {
    return this.formatDin5008(phoneNumberData, defaultCountryData);
  }

  /**
   * format phone number in DIN 5008 format.
   *
   * @param phoneNumberData phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatDin5008(final PhoneNumberInterface phoneNumberData,
      final String countryCode) {
    return this.formatDin5008(phoneNumberData, phoneCountryConstantsProvider
        .getPhoneCountryConstants().getCountriesMap().get(StringUtils.defaultString(countryCode)));
  }

  /**
   * format phone number in DIN 5008 format.
   *
   * @param phoneNumberData phone number to format
   * @param countryData country data
   * @return formated phone number as String
   */
  public final String formatDin5008(final PhoneNumberInterface phoneNumberData,
      final PhoneCountryData countryData) {
    if (phoneNumberData != null && StringUtils.equals(
        countryData.getCountryCodeData().getCountryCode(), phoneNumberData.getCountryCode())) {
      return this.formatDin5008National(phoneNumberData);
    } else {
      return this.formatDin5008International(phoneNumberData);
    }
  }

  /**
   * format phone number in DIN 5008 format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatDin5008WithPos(final ValueWithPos phoneNumber,
      final String countryCode) {
    return valueWithPosDefaults(
        this.formatDin5008WithPos(this.parsePhoneNumber(phoneNumber, countryCode),
            phoneCountryConstantsProvider.getPhoneCountryConstants().getCountriesMap()
                .get(StringUtils.defaultString(countryCode))),
        phoneNumber);
  }

  /**
   * format phone number in DIN 5008 format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @param countryData country data
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatDin5008WithPos(
      final ValueWithPos phoneNumberData, final PhoneCountryData countryData) {
    if (phoneNumberData != null && countryData != null
        && StringUtils.equals(countryData.getCountryCodeData().getCountryCode(),
            phoneNumberData.getValue().getCountryCode())) {
      return this.formatDin5008NationalWithPos(phoneNumberData);
    } else {
      return this.formatDin5008InternationalWithPos(phoneNumberData);
    }
  }

  /**
   * format phone number in DIN 5008 international format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatDin5008International(final String phoneNumber) {
    return this.formatDin5008International(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in DIN 5008 international format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatDin5008International(
      final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(
        this.formatDin5008InternationalWithPos(this.parsePhoneNumber(phoneNumber)), phoneNumber);
  }

  /**
   * format phone number in DIN 5008 international format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatDin5008International(final String phoneNumber,
      final String countryCode) {
    return this.formatDin5008International(this.parsePhoneNumber(phoneNumber, countryCode));
  }

  /**
   * format phone number in DIN 5008 international format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatDin5008International(final PhoneNumberInterface phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      resultNumber.append('+').append(phoneNumberData.getCountryCode()).append(' ');
      if (StringUtils.isNotBlank(phoneNumberData.getAreaCode())) {
        resultNumber.append(phoneNumberData.getAreaCode()).append(' ');
      }
      resultNumber.append(phoneNumberData.getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getExtension())) {
        resultNumber.append('-');
        resultNumber.append(phoneNumberData.getExtension());
      }
    }
    return StringUtils.trimToNull(resultNumber.toString());
  }

  /**
   * format phone number in DIN 5008 international format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatDin5008InternationalWithPos(
      final ValueWithPos phoneNumber, final String countryCode) {
    return valueWithPosDefaults(
        this.formatDin5008InternationalWithPos(this.parsePhoneNumber(phoneNumber, countryCode)),
        phoneNumber);
  }

  /**
   * format phone number in DIN 5008 international format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatDin5008InternationalWithPos(
      final ValueWithPos phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (phoneNumberData == null) {
      return null;
    }
    int cursor =
        formatE123orDin5008InternationalWithPosWithoutExtension(phoneNumberData, resultNumber);
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())
        && StringUtils.isNotBlank(phoneNumberData.getValue().getExtension())) {
      if (resultNumber.length() <= cursor) {
        cursor++;
      }
      resultNumber.append('-');
      resultNumber.append(phoneNumberData.getValue().getExtension());
    }
    return new ValueWithPos<>(StringUtils.trimToNull(resultNumber.toString()), cursor);
  }

  /**
   * format phone number in DIN 5008 national format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatDin5008National(final String phoneNumber) {
    return this.formatDin5008National(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in DIN 5008 national format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatDin5008National(final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(
        this.formatDin5008NationalWithPos(this.parsePhoneNumber(phoneNumber)), phoneNumber);
  }

  /**
   * format phone number in DIN 5008 national format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatDin5008National(final String phoneNumber, final String countryCode) {
    return this.formatDin5008National(this.parsePhoneNumber(phoneNumber, countryCode));
  }

  /**
   * format phone number in DIN 5008 national format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatDin5008National(final PhoneNumberInterface phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      PhoneCountryData phoneCountryData = null;
      for (final PhoneCountryCodeData country : phoneCountryConstantsProvider
          .getPhoneCountryConstants().getCountryCode()) {
        if (StringUtils.equals(country.getCountryCode(), phoneNumberData.getCountryCode())) {
          phoneCountryData = country.getPhoneCountryData();
          break;
        }
      }
      if (phoneCountryData == null) {
        return this.formatDin5008International(phoneNumberData);
      }
      resultNumber.append(phoneCountryData.getTrunkCode());
      if (StringUtils.isNotBlank(phoneNumberData.getAreaCode())) {
        resultNumber.append(phoneNumberData.getAreaCode());
      }
      resultNumber.append(' ');
      resultNumber.append(phoneNumberData.getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getExtension())) {
        resultNumber.append('-');
        resultNumber.append(phoneNumberData.getExtension());
      }
    }
    return StringUtils.trimToNull(resultNumber.toString());
  }

  /**
   * format phone number in DIN 5008 national format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatDin5008NationalWithPos(
      final ValueWithPos phoneNumber, final String countryCode) {
    return valueWithPosDefaults(
        this.formatDin5008NationalWithPos(this.parsePhoneNumber(phoneNumber, countryCode)),
        phoneNumber);
  }

  /**
   * format phone number in DIN 5008 national format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatDin5008NationalWithPos(
      final ValueWithPos phoneNumberData) {
    if (phoneNumberData == null) {
      return null;
    }
    int cursor = phoneNumberData.getPos();
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())) {
      PhoneCountryData phoneCountryData = null;
      for (final PhoneCountryCodeData country : phoneCountryConstantsProvider
          .getPhoneCountryConstants().getCountryCode()) {
        if (StringUtils.equals(country.getCountryCode(),
            phoneNumberData.getValue().getCountryCode())) {
          phoneCountryData = country.getPhoneCountryData();
          break;
        }
      }
      if (phoneCountryData == null) {
        return this.formatDin5008InternationalWithPos(phoneNumberData);
      }
      if (cursor > 0) {
        cursor -= StringUtils.length(phoneNumberData.getValue().getCountryCode());
        cursor += StringUtils.length(phoneCountryData.getTrunkCode());
      }
      resultNumber.append(phoneCountryData.getTrunkCode());
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getAreaCode())) {
        resultNumber.append(phoneNumberData.getValue().getAreaCode());
      }
      if (resultNumber.length() <= cursor) {
        cursor++;
      }
      resultNumber.append(' ');
      resultNumber.append(phoneNumberData.getValue().getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getExtension())) {
        if (resultNumber.length() <= cursor) {
          cursor++;
        }
        resultNumber.append('-');
        resultNumber.append(phoneNumberData.getValue().getExtension());
      }
    }
    return new ValueWithPos<>(StringUtils.trimToNull(resultNumber.toString()), cursor);
  }

  /**
   * format phone number in RFC 3966 format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatRfc3966(final String phoneNumber) {
    return this.formatRfc3966(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in RFC 3966 format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatRfc3966(final String phoneNumber, final String countryCode) {
    return this.formatRfc3966(this.parsePhoneNumber(phoneNumber, countryCode));
  }

  /**
   * format phone number in RFC 3966 format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatRfc3966(final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(this.formatRfc3966WithPos(this.parsePhoneNumber(phoneNumber)),
        phoneNumber);
  }

  /**
   * format phone number in RFC 3966 format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatRfc3966(final PhoneNumberInterface phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      resultNumber.append('+').append(phoneNumberData.getCountryCode()).append('-');
      if (StringUtils.isNotBlank(phoneNumberData.getAreaCode())) {
        resultNumber.append(phoneNumberData.getAreaCode()).append('-');
      }
      resultNumber.append(phoneNumberData.getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getExtension())) {
        resultNumber.append(phoneNumberData.getExtension());
      }
    }
    return StringUtils.trimToNull(resultNumber.toString());
  }

  /**
   * format phone number in RFC 3966 format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatRfc3966WithPos(final ValueWithPos phoneNumber,
      final String countryCode) {
    return valueWithPosDefaults(
        this.formatRfc3966WithPos(this.parsePhoneNumber(phoneNumber, countryCode)), phoneNumber);
  }

  /**
   * format phone number in RFC 3966 format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatRfc3966WithPos(
      final ValueWithPos phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (phoneNumberData == null) {
      return null;
    }
    final int cursor = formatRfc3966orUrlWithPosWithoutExtension(phoneNumberData, resultNumber);
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())
        && StringUtils.isNotBlank(phoneNumberData.getValue().getExtension())) {
      resultNumber.append(phoneNumberData.getValue().getExtension());
    }
    return new ValueWithPos<>(StringUtils.trimToNull(resultNumber.toString()), cursor);
  }

  private int formatRfc3966orUrlWithPosWithoutExtension(
      final ValueWithPos phoneNumberData, final StringBuilder resultNumber) {
    int cursor = phoneNumberData.getPos();
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())) {
      cursor++;
      resultNumber.append('+').append(phoneNumberData.getValue().getCountryCode());
      if (resultNumber.length() <= cursor) {
        cursor++;
      }
      resultNumber.append('-');
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getAreaCode())) {
        resultNumber.append(phoneNumberData.getValue().getAreaCode());
        if (resultNumber.length() <= cursor) {
          cursor++;
        }
        resultNumber.append('-');
      }
      resultNumber.append(phoneNumberData.getValue().getLineNumber());
    }
    return cursor;
  }

  /**
   * format phone number in Microsoft canonical address format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatMs(final String phoneNumber) {
    return this.formatMs(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in Microsoft canonical address format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatMs(final String phoneNumber, final String countryCode) {
    return this.formatMs(this.parsePhoneNumber(phoneNumber, countryCode));
  }

  /**
   * format phone number in Microsoft canonical address format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatMs(final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(this.formatMsWithPos(this.parsePhoneNumber(phoneNumber)),
        phoneNumber);
  }

  /**
   * format phone number in Microsoft canonical address format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatMs(final PhoneNumberInterface phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      resultNumber.append('+').append(phoneNumberData.getCountryCode()).append(' ');
      if (StringUtils.isNotBlank(phoneNumberData.getAreaCode())) {
        resultNumber.append('(').append(phoneNumberData.getAreaCode()).append(") ");
      }
      resultNumber.append(phoneNumberData.getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getExtension())) {
        resultNumber.append(" - ");
        resultNumber.append(phoneNumberData.getExtension());
      }
    }
    return StringUtils.trimToNull(resultNumber.toString());
  }

  /**
   * format phone number in Microsoft canonical address format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatMsWithPos(final ValueWithPos phoneNumber,
      final String countryCode) {
    return valueWithPosDefaults(
        this.formatMsWithPos(this.parsePhoneNumber(phoneNumber, countryCode)), phoneNumber);
  }

  /**
   * format phone number in Microsoft canonical address format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatMsWithPos(
      final ValueWithPos phoneNumberData) {
    if (phoneNumberData == null) {
      return null;
    }
    int cursor = phoneNumberData.getPos();
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())) {
      cursor++;
      resultNumber.append('+').append(phoneNumberData.getValue().getCountryCode());
      if (resultNumber.length() <= cursor) {
        cursor++;
      }
      resultNumber.append(' ');
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getAreaCode())) {
        if (resultNumber.length() <= cursor) {
          cursor++;
        }
        resultNumber.append('(').append(phoneNumberData.getValue().getAreaCode());
        if (resultNumber.length() <= cursor) {
          cursor += 2;
        }
        resultNumber.append(") ");
      }
      resultNumber.append(phoneNumberData.getValue().getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getExtension())) {
        if (resultNumber.length() <= cursor) {
          cursor += 3;
        }
        resultNumber.append(" - ");
        resultNumber.append(phoneNumberData.getValue().getExtension());
      }
    }
    if (cursor < 0) {
      cursor = 0;
    } else if (cursor > resultNumber.length()) {
      cursor = resultNumber.length();
    }
    return new ValueWithPos<>(StringUtils.trimToNull(resultNumber.toString()), cursor);
  }

  /**
   * format phone number in URL format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatUrl(final String phoneNumber) {
    return this.formatUrl(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in URL format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatUrl(final String phoneNumber, final String countryCode) {
    return this.formatUrl(this.parsePhoneNumber(phoneNumber, countryCode));
  }

  /**
   * format phone number in URL format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatUrl(final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(this.formatUrlWithPos(this.parsePhoneNumber(phoneNumber)),
        phoneNumber);
  }

  /**
   * format phone number in URL format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatUrl(final PhoneNumberInterface phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      resultNumber.append('+').append(phoneNumberData.getCountryCode());
      if (StringUtils.isNotBlank(phoneNumberData.getAreaCode())) {
        resultNumber.append('-');
        resultNumber.append(phoneNumberData.getAreaCode());
      }
      resultNumber.append('-');
      resultNumber.append(phoneNumberData.getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getExtension())) {
        resultNumber.append('-');
        resultNumber.append(phoneNumberData.getExtension());
      }
    }
    return StringUtils.trimToNull(resultNumber.toString());
  }

  /**
   * format phone number in URL format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatUrlWithPos(final ValueWithPos phoneNumber,
      final String countryCode) {
    return valueWithPosDefaults(
        this.formatUrlWithPos(this.parsePhoneNumber(phoneNumber, countryCode)), phoneNumber);
  }

  /**
   * format phone number in URL format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatUrlWithPos(
      final ValueWithPos phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (phoneNumberData == null) {
      return null;
    }
    int cursor = formatRfc3966orUrlWithPosWithoutExtension(phoneNumberData, resultNumber);
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())
        && StringUtils.isNotBlank(phoneNumberData.getValue().getExtension())) {
      if (resultNumber.length() <= cursor) {
        cursor++;
      }
      resultNumber.append('-');
      resultNumber.append(phoneNumberData.getValue().getExtension());
    }
    return new ValueWithPos<>(StringUtils.trimToNull(resultNumber.toString()), cursor);
  }

  /**
   * format phone number in Common format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatCommon(final String phoneNumber) {
    return this.formatCommon(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in common format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatCommon(final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(
        this.formatCommonWithPos(this.parsePhoneNumber(phoneNumber), defaultCountryData),
        phoneNumber);
  }

  /**
   * format phone number in common format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatCommon(final PhoneNumberInterface phoneNumberData) {
    return this.formatCommon(phoneNumberData, defaultCountryData);
  }

  /**
   * format phone number in common format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatCommon(final String phoneNumber, final String countryCode) {
    return this.formatCommon(this.parsePhoneNumber(phoneNumber, countryCode),
        phoneCountryConstantsProvider.getPhoneCountryConstants().getCountriesMap()
            .get(StringUtils.defaultString(countryCode)));
  }

  /**
   * format phone number in common format.
   *
   * @param phoneNumberData phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatCommon(final PhoneNumberInterface phoneNumberData,
      final String countryCode) {
    return this.formatCommon(phoneNumberData, phoneCountryConstantsProvider
        .getPhoneCountryConstants().getCountriesMap().get(StringUtils.defaultString(countryCode)));
  }

  /**
   * format phone number in common format.
   *
   * @param phoneNumberData phone number to format
   * @param countryData country data
   * @return formated phone number as String
   */
  public final String formatCommon(final PhoneNumberInterface phoneNumberData,
      final PhoneCountryData countryData) {
    if (phoneNumberData != null && countryData != null && StringUtils.equals(
        countryData.getCountryCodeData().getCountryCode(), phoneNumberData.getCountryCode())) {
      return this.formatCommonNational(phoneNumberData);
    } else {
      return this.formatCommonInternational(phoneNumberData);
    }
  }

  /**
   * format phone number in common format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatCommonWithPos(final ValueWithPos phoneNumber,
      final String countryCode) {
    return valueWithPosDefaults(
        this.formatCommonWithPos(this.parsePhoneNumber(phoneNumber, countryCode),
            phoneCountryConstantsProvider.getPhoneCountryConstants().getCountriesMap()
                .get(StringUtils.defaultString(countryCode))),
        phoneNumber);
  }

  /**
   * format phone number in common format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @param countryData country data
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatCommonWithPos(
      final ValueWithPos phoneNumberData, final PhoneCountryData countryData) {
    if (phoneNumberData != null && countryData != null
        && StringUtils.equals(countryData.getCountryCodeData().getCountryCode(),
            phoneNumberData.getValue().getCountryCode())) {
      return this.formatCommonNationalWithPos(phoneNumberData);
    } else {
      return this.formatCommonInternationalWithPos(phoneNumberData);
    }
  }

  /**
   * format phone number in Common international format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatCommonInternational(final String phoneNumber) {
    return this.formatCommonInternational(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in common international format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatCommonInternational(
      final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(
        this.formatCommonInternationalWithPos(this.parsePhoneNumber(phoneNumber)), phoneNumber);
  }

  /**
   * format phone number in common international format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatCommonInternational(final String phoneNumber,
      final String countryCode) {
    return this.formatCommonInternational(this.parsePhoneNumber(phoneNumber, countryCode));
  }

  /**
   * format phone number in Common international format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatCommonInternational(final PhoneNumberInterface phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      PhoneCountryData phoneCountryData = null;
      for (final PhoneCountryCodeData country : phoneCountryConstantsProvider
          .getPhoneCountryConstants().getCountryCode()) {
        if (StringUtils.equals(country.getCountryCode(), phoneNumberData.getCountryCode())) {
          phoneCountryData = country.getPhoneCountryData();
          break;
        }
      }
      if (phoneCountryData == null) {
        return null;
      }
      resultNumber.append('+').append(phoneNumberData.getCountryCode()).append(' ');
      resultNumber.append('(').append(phoneCountryData.getTrunkCode()).append(')');
      if (StringUtils.isNotBlank(phoneNumberData.getAreaCode())) {
        resultNumber.append(phoneNumberData.getAreaCode()).append(' ');
      }
      resultNumber.append(phoneNumberData.getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getExtension())) {
        resultNumber.append('-').append(phoneNumberData.getExtension());
      }
    }
    return StringUtils.trimToNull(resultNumber.toString());
  }

  /**
   * format phone number in common international format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatCommonInternationalWithPos(
      final ValueWithPos phoneNumber, final String countryCode) {
    return valueWithPosDefaults(
        this.formatCommonInternationalWithPos(this.parsePhoneNumber(phoneNumber, countryCode)),
        phoneNumber);
  }

  /**
   * format phone number in common international format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatCommonInternationalWithPos(
      final ValueWithPos phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (phoneNumberData == null) {
      return null;
    }
    int cursor = phoneNumberData.getPos();
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())) {
      PhoneCountryData phoneCountryData = null;
      for (final PhoneCountryCodeData country : phoneCountryConstantsProvider
          .getPhoneCountryConstants().getCountryCode()) {
        if (StringUtils.equals(country.getCountryCode(),
            phoneNumberData.getValue().getCountryCode())) {
          phoneCountryData = country.getPhoneCountryData();
          break;
        }
      }
      if (phoneCountryData == null) {
        return null;
      }
      cursor++;
      resultNumber.append('+').append(phoneNumberData.getValue().getCountryCode());
      if (resultNumber.length() <= cursor) {
        cursor++;
      }
      resultNumber.append(' ');
      if (resultNumber.length() <= cursor) {
        cursor += 2;
      }
      resultNumber.append('(').append(phoneCountryData.getTrunkCode());
      if (resultNumber.length() <= cursor) {
        cursor++;
      }
      resultNumber.append(')');
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getAreaCode())) {
        resultNumber.append(phoneNumberData.getValue().getAreaCode());
        if (resultNumber.length() <= cursor) {
          cursor++;
        }
        resultNumber.append(' ');
      }
      resultNumber.append(phoneNumberData.getValue().getLineNumber());
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getExtension())) {
        if (resultNumber.length() <= cursor) {
          cursor++;
        }
        resultNumber.append('-');
        resultNumber.append(phoneNumberData.getValue().getExtension());
      }
    }
    return new ValueWithPos<>(StringUtils.trimToNull(resultNumber.toString()), cursor);
  }

  /**
   * format phone number in Common national format.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as String
   */
  public final String formatCommonNational(final String phoneNumber) {
    return this.formatCommonNational(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in common national format.
   *
   * @param phoneNumber phone number to format
   * @param countryCode iso code of country
   * @return formated phone number as String
   */
  public final String formatCommonNational(final String phoneNumber, final String countryCode) {
    return this.formatCommonNational(this.parsePhoneNumber(phoneNumber, countryCode));
  }

  /**
   * format phone number in common national format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatCommonNational(final ValueWithPos phoneNumber) {
    return valueWithPosDefaults(
        this.formatCommonNationalWithPos(this.parsePhoneNumber(phoneNumber)), phoneNumber);
  }

  /**
   * format phone number in Common national format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as String
   */
  public final String formatCommonNational(final PhoneNumberInterface phoneNumberData) {
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      PhoneCountryData phoneCountryData = null;
      for (final PhoneCountryCodeData country : phoneCountryConstantsProvider
          .getPhoneCountryConstants().getCountryCode()) {
        if (StringUtils.equals(country.getCountryCode(), phoneNumberData.getCountryCode())) {
          phoneCountryData = country.getPhoneCountryData();
          break;
        }
      }
      if (phoneCountryData == null) {
        return null;
      }
      resultNumber.append(phoneCountryData.getTrunkCode()).append(' ');
      resultNumber.append(this.groupIntoParts(phoneNumberData.getAreaCode(), 2));
      resultNumber.append(" / ");
      resultNumber.append(this.groupIntoParts(phoneNumberData.getLineNumber(), 2));
      if (StringUtils.isNotBlank(phoneNumberData.getExtension())) {
        resultNumber.append(" - ");
        resultNumber.append(this.groupIntoParts(phoneNumberData.getExtension(), 2));
      }
    }
    return StringUtils.trimToNull(resultNumber.toString());
  }

  /**
   * format phone number in common national format with cursor position handling.
   *
   * @param phoneNumber phone number as String to format with cursor position
   * @param countryCode iso code of country
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatCommonNationalWithPos(
      final ValueWithPos phoneNumber, final String countryCode) {
    return valueWithPosDefaults(
        this.formatCommonNationalWithPos(this.parsePhoneNumber(phoneNumber, countryCode)),
        phoneNumber);
  }

  /**
   * format phone number in common national format with cursor position handling.
   *
   * @param phoneNumberData phone number to format with cursor position
   * @return formated phone number as String with new cursor position
   */
  public final ValueWithPos formatCommonNationalWithPos(
      final ValueWithPos phoneNumberData) {
    if (phoneNumberData == null) {
      return null;
    }
    int cursor = phoneNumberData.getPos();
    final StringBuilder resultNumber = new StringBuilder();
    if (isPhoneNumberNotEmpty(phoneNumberData.getValue())) {
      PhoneCountryData phoneCountryData = null;
      for (final PhoneCountryCodeData country : phoneCountryConstantsProvider
          .getPhoneCountryConstants().getCountryCode()) {
        if (StringUtils.equals(country.getCountryCode(),
            phoneNumberData.getValue().getCountryCode())) {
          phoneCountryData = country.getPhoneCountryData();
          break;
        }
      }
      if (phoneCountryData == null) {
        return null;
      }
      if (cursor > 0) {
        cursor -= StringUtils.length(phoneNumberData.getValue().getCountryCode());
        cursor += StringUtils.length(phoneCountryData.getTrunkCode());
      }
      resultNumber.append(phoneCountryData.getTrunkCode());
      if (resultNumber.length() <= cursor) {
        cursor++;
      }
      resultNumber.append(' ');
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getAreaCode())) {
        final ValueWithPos areaCode = this.groupIntoParts(
            new ValueWithPos<>(phoneNumberData.getValue().getAreaCode(), cursor),
            resultNumber.length(), 2);
        cursor = areaCode.getPos();
        resultNumber.append(areaCode.getValue());
      }
      if (resultNumber.length() <= cursor) {
        cursor += 3;
      }
      resultNumber.append(" / ");
      final ValueWithPos lineNumber = this.groupIntoParts(
          new ValueWithPos<>(phoneNumberData.getValue().getLineNumber(), cursor),
          resultNumber.length(), 2);
      cursor = lineNumber.getPos();
      resultNumber.append(lineNumber.getValue());
      if (StringUtils.isNotBlank(phoneNumberData.getValue().getExtension())) {
        if (resultNumber.length() <= cursor) {
          cursor += 3;
        }
        resultNumber.append(" - ");
        final ValueWithPos extension = this.groupIntoParts(
            new ValueWithPos<>(phoneNumberData.getValue().getExtension(), cursor),
            resultNumber.length(), 2);
        cursor = extension.getPos();
        resultNumber.append(extension.getValue());
      }
    }
    return new ValueWithPos<>(StringUtils.trimToNull(resultNumber.toString()), cursor);
  }

  private ValueWithPos groupIntoParts(final ValueWithPos string, final int length,
      final int blockLength) {
    if (string == null || string.getValue() == null) {
      return new ValueWithPos<>(StringUtils.EMPTY, blockLength);
    }
    final StringBuilder formatedSb = new StringBuilder();
    int pos = 0;
    for (final char charCode : string.getValue().toCharArray()) {
      if (CharUtils.isAsciiNumeric(charCode)) {
        if (pos > 0 && pos % blockLength == 0) {
          if (formatedSb.length() + length <= string.getPos()) {
            string.setPos(string.getPos() + 1);
          }
          formatedSb.append(' ');
        }
        formatedSb.append(charCode);
        pos++;
      }
    }
    string.setValue(formatedSb.toString());
    return string;
  }

  private String groupIntoParts(final String string, final int blockLength) {
    if (string == null) {
      return StringUtils.EMPTY;
    }
    final StringBuilder formatedSb = new StringBuilder();
    int pos = 0;
    for (final char charCode : string.toCharArray()) {
      if (CharUtils.isAsciiNumeric(charCode)) {
        if (pos > 0 && pos % blockLength == 0) {
          formatedSb.append(' ');
        }
        formatedSb.append(charCode);
        pos++;
      }
    }
    return formatedSb.toString();
  }

  private ValueWithPos valueWithPosDefaults(final ValueWithPos formatValueWithPos,
      final ValueWithPos defaultNumber) {
    if (formatValueWithPos != null && (StringUtils.isEmpty(formatValueWithPos.getValue()) //
        || StringUtils.startsWith(defaultNumber.getValue(), formatValueWithPos.getValue())
            && !Character.isDigit(defaultNumber.getValue()
                .charAt(StringUtils.length(defaultNumber.getValue()) - 1)))) {
      formatValueWithPos.setValue(defaultNumber.getValue());
      formatValueWithPos.setPos(defaultNumber.getPos());
    }
    if (defaultNumber != null) {
      formatValueWithPos.setOriginalValue(defaultNumber.getValue());
    }
    return formatValueWithPos;
  }

  /**
   * format phone number to index.
   *
   * @param phoneNumber phone number as String to format
   * @return formated phone number as Long
   */
  public final Long formatIndex(final String phoneNumber) {
    return this.formatIndex(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * format phone number in URL format.
   *
   * @param phoneNumberData phone number to format
   * @return formated phone number as Long
   */
  public final Long formatIndex(final PhoneNumberInterface phoneNumberData) {
    if (isPhoneNumberNotEmpty(phoneNumberData)) {
      return Long.valueOf(StringUtils.defaultString(phoneNumberData.getCountryCode())
          + StringUtils.defaultString(phoneNumberData.getAreaCode())
          + StringUtils.defaultString(phoneNumberData.getLineNumber())
          + StringUtils.defaultString(phoneNumberData.getExtension()));
    }
    return null;
  }

  /**
   * check if phone number is empty.
   *
   * @param phoneNumberData phone number to check
   * @return true if number is empty
   */
  public final boolean isPhoneNumberEmpty(final PhoneNumberInterface phoneNumberData) {
    return phoneNumberData == null || StringUtils.isBlank(phoneNumberData.getCountryCode())
        || StringUtils.isBlank(phoneNumberData.getLineNumber());
  }

  /**
   * check if phone number is not empty.
   *
   * @param phoneNumberData phone number to check
   * @return true if number is not empty
   */
  public final boolean isPhoneNumberNotEmpty(final PhoneNumberInterface phoneNumberData) {
    return !isPhoneNumberEmpty(phoneNumberData);
  }

  /**
   * get suggestions.
   *
   * @param search search string
   * @param limit limit entries
   * @return list of phone number data
   */
  public final List getSuggstions(final String search, final int limit) {
    return this.getSuggstions(search, limit, Locale.ROOT);
  }

  /**
   * get suggestions.
   *
   * @param search search string
   * @param limit limit entries
   * @param locale locale
   * @return list of phone number data
   */
  public final List getSuggstions(final String search, final int limit,
      final Locale locale) {
    final List suggestList = new ArrayList<>(limit);
    final String cleanedPhoneNumber = cleanString(search);
    PhoneCountryCodeData foundCounty = null;
    final List possibleCountries = new ArrayList<>(limit);
    for (final PhoneCountryCodeData countryCode : phoneCountryConstantsProvider
        .getPhoneCountryConstants(locale).getCountryCode()) {
      if (cleanedPhoneNumber.startsWith(countryCode.getCountryCode())) {
        foundCounty = countryCode;
        break;
      }
      if (countryCode.getCountryCode().startsWith(cleanedPhoneNumber)) {
        possibleCountries.add(countryCode);
      }
    }
    if (foundCounty == null) {
      // we don't have found a matching country, show possible countries
      for (final PhoneCountryCodeData country : possibleCountries) {
        final PhoneNumberData entry = new PhoneNumberData();
        entry.setCountryCode(country.getCountryCode());
        entry.setCountryName(country.getCountryCodeName());
        suggestList.add(entry);
      }
    } else {
      // we do have a country, search for possible area codes
      final String phoneNumberWork =
          StringUtils.substring(cleanedPhoneNumber, foundCounty.getCountryCode().length());
      for (final PhoneAreaCodeData areaCode : foundCounty.getAreaCodeData()) {
        if (!areaCode.isRegEx() && areaCode.getAreaCode().startsWith(phoneNumberWork)) {
          final PhoneNumberData entry = new PhoneNumberData();
          entry.setCountryCode(foundCounty.getCountryCode());
          entry.setCountryName(foundCounty.getCountryCodeName());
          entry.setAreaCode(areaCode.getAreaCode());
          entry.setAreaName(areaCode.getAreaName());
          suggestList.add(entry);
        }
      }
    }
    Collections.sort(suggestList, new PhoneNumberSuggestComperator());
    if (suggestList.size() >= limit) {
      return suggestList.subList(0, limit);
    }
    return suggestList;
  }

  /**
   * detect country code for given phone number.
   *
   * @param phoneNumber phone number as String to detect country code
   * @return country enum with country code
   */
  public final CountryEnum detectCountryCode(final String phoneNumber) {
    return this.detectCountryCode(this.parsePhoneNumber(phoneNumber));
  }

  /**
   * detect country code for given phone number.
   *
   * @param phoneNumberData phone number to detect country code fro
   * @return country enum with country code
   */
  public final CountryEnum detectCountryCode(final PhoneNumberInterface phoneNumberData) {

    if (phoneNumberData == null || phoneNumberData.getCountryCode() == null) {
      return null;
    }

    switch (phoneNumberData.getCountryCode()) {
      case "1":
        switch (StringUtils.defaultString(phoneNumberData.getAreaCode())) {
          case "242":
            return CountryEnum.BS;
          case "246":
            return CountryEnum.BB;
          case "264":
            return CountryEnum.AI;
          case "268":
            return CountryEnum.AG;
          case "284":
            return CountryEnum.VG;
          case "340":
            return CountryEnum.VI;
          case "345":
            return CountryEnum.KY;
          case "441":
            return CountryEnum.BM;
          case "473":
            return CountryEnum.GD;
          case "649":
            return CountryEnum.TC;
          case "664":
            return CountryEnum.MS;
          case "670":
            return CountryEnum.MP;
          case "671":
            return CountryEnum.GU;
          case "684":
            return CountryEnum.AS;
          case "721":
            return CountryEnum.SX;
          case "758":
            return CountryEnum.LC;
          case "767":
            return CountryEnum.DM;
          case "784":
            return CountryEnum.VC;
          case "787":
          case "939":
            return CountryEnum.PR;
          case "809":
          case "829":
          case "849":
            return CountryEnum.DO;
          case "868":
            return CountryEnum.TT;
          case "869":
            return CountryEnum.KN;
          case "876":
            return CountryEnum.JM;
          case "204":
          case "226":
          case "236":
          case "249":
          case "250":
          case "289":
          case "306":
          case "343":
          case "365":
          case "403":
          case "416":
          case "418":
          case "431":
          case "437":
          case "438":
          case "450":
          case "506":
          case "514":
          case "519":
          case "579":
          case "581":
          case "587":
          case "600":
          case "604":
          case "613":
          case "639":
          case "647":
          case "705":
          case "709":
          case "778":
          case "780":
          case "782":
          case "807":
          case "819":
          case "867":
          case "873":
          case "902":
          case "905":
            return CountryEnum.CA;
          default:
            return CountryEnum.US;
        }
      case "599":
        if ("9".equals(phoneNumberData.getAreaCode())) {
          return CountryEnum.CW;
        }
        return CountryEnum.BQ;
      case "7":
        if (StringUtils.startsWith(phoneNumberData.getAreaCode(), "6")
            || StringUtils.startsWith(phoneNumberData.getAreaCode(), "7")) {
          return CountryEnum.KZ;
        }
        return CountryEnum.RU;
      default:
        final Optional countryCodeData =
            phoneCountryConstantsProvider.getPhoneCountryConstants().getCountryCode().stream()
                .filter(entry -> entry.getCountryCode()
                    .equals(StringUtils.defaultString(phoneNumberData.getCountryCode())))
                .findFirst();

        if (countryCodeData.isPresent()) {
          final String phoneCountryCode =
              countryCodeData.get().getPhoneCountryData().getCountryCode();
          if (phoneCountryCode.length() == 2) {
            return CountryEnum.valueOf(phoneCountryCode);
          }
        }
        return null;
    }
  }

  private String cleanString(final String phoneNumber) {
    final StringBuilder cleanupString = new StringBuilder(phoneNumber.length());
    for (final char character : phoneNumber.toCharArray()) {
      if (character >= '0' && character <= '9') {
        cleanupString.append(character);
      }
    }
    return cleanupString.toString();
  }

  public boolean isInitialized() {
    return phoneCountryConstantsProvider.hasPhoneCountryConstants();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy