Rufnummernblöcke in der Telephone Bibliothek

I will not be at the mercy of the telephone!

C. S. Lewis

Nachdem in den letzten Beiträgen zur Bibliothek Telephone, die Bean Validierung und der Jackson Support hinzugefügt wurden, geht es in diesem Beitrag um eine fachliche Ergänzung.

Bisher behandelt die Bibliothek nur nationale und internationale Telefonnummern. In diesem Beitrag kommen Rufnummernblöcke hinzu. Ein Rufnummernblock ist eine Liste von aufeinanderfolgende Rufnummern. Im folgenden Beispiel sind 10 Rufnummern dargestellt, die alle die gemeinsame Basisrufnummer 1122 besitzen und die Durchwahlen 0 bis 9.

+49 5223 11220
+49 5223 11221
+49 5223 11222
+49 5223 11223
+49 5223 11224
+49 5223 11225
+49 5223 11226
+49 5223 11227
+49 5223 11228
+49 5223 11229

Um einen solchen Rufnummernblock anzugeben ist die Liste aller Durchwahlen keine besonders kluge Idee, da es auch Rufnummernblöcke mit 100 Durchwahlen geben kann. Eine einfachere Darstellung verwendet die Vorwahl, die Basisrufnummer und die Menge der Durchwahlen in Form eines Intervalls oder eines Musters. Im Folgenden vier Darstellungsvarianten für einstellige Durchwahlen und vier Varianten für zweistellige Durchwahlen.

+49 5223 1122 0-9
+49 5223 1122 [0-9]
+49 5223 1122 X
+49 5223 1122 [X]

+49 5223 1122 00-99
+49 5223 1122 [00-99]
+49 5223 1122 XX
+49 5223 1122 [XX]

Die Darstellung mit eckigen Klammern dient der besseren Lesbarkeit und hat ansonsten keine weitere Bewandtnis.

Wie bei den Klassen InternationalPhoneNumber und NationalPhoneNumber kann die Darstellung der PhoneNumberBlock Klasse für das Parsen und Formatieren angepasst werden.

assertEquals("+49 5223 1122 [00-99]", phoneNumberBlock.format(ofPattern("+ccc nnn sss[-eee] ZZZ")));
assertEquals("+49 5223 1122 00-99", phoneNumberBlock.format(ofPattern("+ccc nnn sss[-eee] zzz")));
assertEquals("+49 5223 1122 [00-99]", phoneNumberBlock.format(ofPattern("+ccc nnn sss[-eee] BBB")));
assertEquals("+49 5223 1122 00-99", phoneNumberBlock.format(ofPattern("+ccc nnn sss[-eee] bbb")));
assertEquals("+49 5223 1122 [XX]", phoneNumberBlock.format(ofPattern("+ccc nnn sss[-eee] XXX")));
assertEquals("+49 5223 1122 XX", phoneNumberBlock.format(ofPattern("+ccc nnn sss[-eee] xxx")));

Zu den bisherigen Pattern +, i, I, c, a, n, s und e kommen noch die Pattern b, B, x, X, z und Z hinzu. Die Pattern b und B stellen die Durchwahlen als Intervall ohne bzw. mit eckigen Klammern dar. Die Pattern x und X stellen die Durchwahlen als Muster ohne bzw. mit eckigen Klammern dar. Als letztes stellen die Pattern z und Z die Durchwahlen als Intervall ohne bzw. mit eckigen Klammern dar. Der Unterschied zu den vorherigen Pattern ist die Möglichkeit Eingaben zu parsen, die entweder Intervall oder Muster sind. Alle anderen Pattern parsen nur das Format, dass sie auch darstellen.

assertEquals("+49 5223 1122 00-99", phoneNumberBlock.format(PhoneNumberFormatter.INTERNATIONAL_BLOCK));
assertEquals("+49 5223 1122 00-99", phoneNumberBlock.toString());
assertEquals("05223 1122 00-99", phoneNumberBlock.format(PhoneNumberFormatter.NATIONAL_BLOCK));

Für die PhoneNumberBlock Klasse existieren zwei vordefinierte Formatter. Der PhoneNumberFormatter.INTERNATIONAL_BLOCK stellt die Blöcke mit internationaler Vorwahl dar und PhoneNumberFormatter.NATIONAL_BLOCK stellt sie mit nationaler Vorwahl dar.

Zur Verarbeitung der neuen Pattern wird die zentrale Switch-Anweisung um die zusätzlichen Cases erweitert. Der Unterschied zwischen den Anweisungen mit groß- bzw. kleingeschriebenen Buchstaben sind die LiteralPart Instanzen für die eckigen Klammern.

case 'b':
  active.parts.add(new BlockPart(6, length));
  active.parts.add(new LiteralPart('-'));
  active.parts.add(new BlockPart(7, length));
  break;
case 'B':
  active.parts.add(new LiteralPart('['));
  active.parts.add(new BlockPart(6, length));
  active.parts.add(new LiteralPart('-'));
  active.parts.add(new BlockPart(7, length));
  active.parts.add(new LiteralPart(']'));
  break;
case 'x':
  active.parts.add(new BlockPatternPart(length));
  break;
case 'X':
  active.parts.add(new LiteralPart('['));
  active.parts.add(new BlockPatternPart(length));
  active.parts.add(new LiteralPart(']'));
  break;
case 'z':
  active.parts.add(new BlockPatternOrBlockPart(length));
  break;
case 'Z':
  active.parts.add(new LiteralPart('['));
  active.parts.add(new BlockPatternOrBlockPart(length));
  active.parts.add(new LiteralPart(']'));
  break;

Die BlockPart, BlockPatternPart und BlockPatternOrBlockPart Instanzen bekommen jeweils die maximal zulässige Länge der Eingaben übergeben. Dies unterscheide diese PhoneNumberPart Implementierungen von den bisherigen, bei denen die Länge nicht speziell geprüft wurde.

Der letzte interessante Teil der Implementierung ist die parseBlock Methode. Sie wertet die einzelnen PhoneNumberPart Instanzen aus, aus denen das aktuelle Format aufgebaut ist.

public PhoneNumberBlock parseBlock(String text) {
    PhoneNumberFormatterContext context = new PhoneNumberFormatterContext(this);
    int pos = 0;
    for (PhoneNumberPart part : partList) {
        pos = part.parse(context, text, pos);
        if (pos < 0) {
            throw new IllegalArgumentException("parse exception: " + pos + " " + text);
        }
    }
    NationalPhoneNumber nationalPhoneNumber;
    CharSequence[] parts = context.parts;
    if (parts[5] != null) {
        nationalPhoneNumber = NationalPhoneNumber
                .of(Objects.toString(parts[2], "0"), String.valueOf(parts[3]), String.valueOf(parts[4]), String.valueOf(parts[5]));
    } else {
        nationalPhoneNumber = NationalPhoneNumber.of(Objects.toString(parts[2], "0"), String.valueOf(parts[3]), String.valueOf(parts[4]));
    }
    InternationalPhoneNumber internationalPhoneNumber = InternationalPhoneNumber
            .of(Objects.toString(parts[0], "00"), Objects.toString(parts[1], "49"), nationalPhoneNumber);
    String blockStart = Objects.toString(parts[6], null);
    String blockEnd = Objects.toString(parts[7], null);
    if (parts[8] != null) {
        blockStart = blockStart == null ? "0".repeat(parts[8].length()) : blockStart;
        blockEnd = blockEnd == null ? "9".repeat(parts[8].length()) : blockEnd;
    }
    return PhoneNumberBlock.of(internationalPhoneNumber, blockStart, blockEnd);
}

Nachdem die Basis-Telefonnummer erstellt wurde, werden am Ende der Methode die Intervallgrenzen des Blocks bestimmt. Sind die Felder part[6] und part[7] ungleich null, dann sind blockStart und blockEnd gesetzt, ansonsten werden die Intervallgrenzen mit Hilfe von Feld part[8] gesetzt. Dieses Feld enthält ein mögliches Blockpattern und bestimmt mit seiner Länge die 0 bzw. 9 Sequenzen für blockStart und blockEnd.

Am Ende wird nun die Factory-Methode für PhoneNumberBlock Methoden aufgerufen und bei validen Parametern ein neuer Rufnummernblock erzeugt.

Schreibe einen Kommentar