de.jfachwert.post.Postfach.java Source code

Java tutorial

Introduction

Here is the source code for de.jfachwert.post.Postfach.java

Source

/*
 * Copyright (c) 2017 by Oliver Boehm
 *
 * Licensed 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.
 *
 * (c)reated 21.02.2017 by oboehm (ob@oasd.de)
 */
package de.jfachwert.post;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import de.jfachwert.Fachwert;
import de.jfachwert.pruefung.exception.InvalidValueException;
import de.jfachwert.pruefung.exception.LocalizedIllegalArgumentException;
import de.jfachwert.util.ToFachwertSerializer;
import org.apache.commons.lang3.StringUtils;

import javax.validation.ValidationException;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * Ein Postfach besteht aus einer Nummer ohne fuehrende Nullen und einer
 * Postleitzahl mit Ortsangabe. Die Nummer selbst ist optional, wenn die
 * durch die Postleitzahl bereits das Postfach abgebildet wird.
 * <p>
 * Im Englischen wird das Postfach oft als POB (Post Office Box) bezeichnet.
 * </p>
 *
 * @author oboehm
 * @since 0.2 (19.06.2017)
 */
@JsonSerialize(using = ToFachwertSerializer.class)
public class Postfach implements Fachwert {

    private final BigInteger nummer;
    private final Ort ort;

    /**
     * Zerlegt den uebergebenen String in seine Einzelteile und validiert sie.
     * Folgende Heuristiken werden fuer die Zerlegung herangezogen:
     * <ul>
     *     <li>Format ist "Postfach, Ort" oder nur "Ort" (mit PLZ)</li>
     *     <li>Postfach ist vom Ort durch Komma oder Zeilenvorschub getrennt</li>
     * </ul>
     *
     * @param postfach z.B. "Postfach 98765, 12345 Entenhausen"
     */
    public Postfach(String postfach) {
        this(split(postfach));
    }

    private Postfach(String[] postfach) {
        this(postfach[0], postfach[1]);
    }

    /**
     * Erzeugt ein Postfach ohne Postfachnummer. D.h. die PLZ des Ortes
     * adressiert bereits das Postfach.
     *
     * @param ort gueltiger Ort mit PLZ
     */
    public Postfach(Ort ort) {
        this.ort = ort;
        this.nummer = null;
        validate(ort);
    }

    /**
     * Erzeugt ein Postfach mit Postfachnummer. Wenn die uebergebene Nummer
     * leer ist, wird ein Postfach ohne Postfachnummer erzeugt.
     * 
     * @param nummer z.B. "12 34 56"
     * @param ort Ort mit Postleitzahl
     */
    public Postfach(String nummer, String ort) {
        this(toNumber(nummer), new Ort(ort));
    }

    /**
     * Erzeugt ein neues Postfach.
     *
     * @param map mit den einzelnen Elementen fuer "plz", "ortsname" und
     *            "nummer".
     */
    @JsonCreator
    public Postfach(Map<String, String> map) {
        this(toNumber(map.get("nummer")), new Ort(PLZ.of(map.get("plz")), map.get("ortsname")));
    }

    /**
     * Erzeugt ein Postfach.
     *
     * @param nummer positive Zahl ohne fuehrende Null
     * @param ort gueltiger Ort mit PLZ
     */
    public Postfach(long nummer, Ort ort) {
        this(BigInteger.valueOf(nummer), ort);
    }

    /**
     * Erzeugt ein Postfach.
     *
     * @param nummer positive Zahl ohne fuehrende Null
     * @param ort gueltiger Ort mit PLZ
     */
    public Postfach(BigInteger nummer, Ort ort) {
        this.nummer = nummer;
        this.ort = ort;
        verify(nummer, ort);
    }

    /**
     * Erzeugt ein Postfach.
     * 
     * @param nummer positive Zahl oder leer
     * @param ort Ort
     */
    public Postfach(Optional<BigInteger> nummer, Ort ort) {
        this.nummer = nummer.orElse(null);
        this.ort = ort;
        if (this.nummer == null) {
            verify(ort);
        } else {
            verify(nummer.get(), ort);
        }
    }

    /**
     * Zerlegt den uebergebenen String in seine Einzelteile und validiert sie.
     * Folgende Heuristiken werden fuer die Zerlegung herangezogen:
     * <ul>
     *     <li>Format ist "Postfach, Ort" oder nur "Ort" (mit PLZ)</li>
     *     <li>Postfach ist vom Ort durch Komma oder Zeilenvorschub getrennt</li>
     * </ul>
     *
     * @param postfach z.B. "Postfach 98765, 12345 Entenhausen"
     * @return Postfach
     */
    public static Postfach of(String postfach) {
        return new Postfach(postfach);
    }

    /**
     * Erzeugt ein Postfach.
     *
     * @param nummer positive Zahl ohne fuehrende Null
     * @param ort gueltiger Ort mit PLZ
     * @return Postfach
     */
    public static Postfach of(long nummer, Ort ort) {
        return new Postfach(nummer, ort);
    }

    /**
     * Erzeugt ein Postfach.
     *
     * @param nummer positive Zahl ohne fuehrende Null
     * @param ort gueltiger Ort mit PLZ
     * @return Postfach
     */
    public static Postfach of(BigInteger nummer, Ort ort) {
        return new Postfach(nummer, ort);
    }

    /**
     * Zerlegt das uebergebene Postfach in seine Einzelteile und validiert sie.
     * Folgende Heuristiken werden fuer die Zerlegung herangezogen:
     * <ul>
     *     <li>Format ist "Postfach, Ort" oder nur "Ort" (mit PLZ)</li>
     *     <li>Postfach ist vom Ort durch Komma oder Zeilenvorschub getrennt</li>
     * </ul>
     *
     * @param postfach z.B. "Postfach 98765, 12345 Entenhausen"
     */
    public static void validate(String postfach) {
        String[] lines = split(postfach);
        toNumber(lines[0]);
        Ort ort = new Ort(lines[1]);
        if (!ort.getPLZ().isPresent()) {
            throw new InvalidValueException(postfach, "postal_code");
        }
    }

    private static String[] split(String postfach) {
        String[] lines = StringUtils.trimToEmpty(postfach).split("[,\\n$]");
        String[] splitted = { "", lines[0] };
        if (lines.length == 2) {
            splitted = lines;
        } else if (lines.length > 2) {
            throw new InvalidValueException(postfach, "post_office_box");
        }
        return splitted;
    }

    private static Optional<BigInteger> toNumber(String number) {
        if (StringUtils.isBlank(number)) {
            return Optional.empty();
        }
        String unformatted = StringUtils.replaceAll(number, "Postfach|\\s+", "");
        try {
            return Optional.of(new BigInteger(unformatted));
        } catch (NumberFormatException nfe) {
            throw new InvalidValueException(number, "number", nfe);
        }
    }

    /**
     * Validiert das uebergebene Postfach auf moegliche Fehler.
     *
     * @param nummer    Postfach-Nummer (muss positiv sein)
     * @param ort       Ort mit PLZ
     */
    public static void validate(BigInteger nummer, Ort ort) {
        if (nummer.compareTo(BigInteger.ONE) < 0) {
            throw new InvalidValueException(nummer, "number");
        }
        validate(ort);
    }

    private static void verify(BigInteger nummer, Ort ort) {
        try {
            validate(nummer, ort);
        } catch (ValidationException ex) {
            throw new LocalizedIllegalArgumentException(ex);
        }
    }

    /**
     * Ueberprueft, ob der uebergebene Ort tatsaechlich ein PLZ enthaelt.
     *
     * @param ort Ort mit PLZ
     */
    public static void validate(Ort ort) {
        if (!ort.getPLZ().isPresent()) {
            throw new InvalidValueException(ort, "postal_code");
        }
    }

    private static void verify(Ort ort) {
        try {
            validate(ort);
        } catch (ValidationException ex) {
            throw new LocalizedIllegalArgumentException(ex);
        }
    }

    /**
     * Liefert die Postfach-Nummer als normale Zahl. Da die Nummer optional
     * sein kann, wird sie als {@link Optional} zurueckgeliefert.
     *
     * @return z.B. 815
     */
    public Optional<BigInteger> getNummer() {
        if (nummer == null) {
            return Optional.empty();
        } else {
            return Optional.of(nummer);
        }
    }

    /**
     * Liefert die Postfach-Nummer als formattierte Zahl. Dies macht natuerlich
     * nur Sinn, wenn diese Nummer gesetzt ist. Daher wird eine
     * {@link IllegalStateException} geworfen, wenn dies nicht der Fall ist.
     *
     * @return z.B. "8 15"
     */
    @SuppressWarnings("squid:S3655")
    public String getNummerFormatted() {
        if (!this.getNummer().isPresent()) {
            throw new IllegalStateException("no number present");
        }
        BigInteger hundert = BigInteger.valueOf(100);
        StringBuilder formatted = new StringBuilder();
        for (BigInteger i = this.getNummer().get(); i.compareTo(BigInteger.ONE) > 0; i = i.divide(hundert)) {
            formatted.insert(0, " " + i.remainder(hundert));
        }
        return formatted.toString().trim();
    }

    /**
     * Liefert die Postleitzahl. Ohne gueltige Postleitzahl kann kein Postfach
     * angelegt werden, weswegen hier immer eine PLZ zurueckgegeben wird.
     *
     * @return z.B. 09876
     */
    @SuppressWarnings("squid:S3655")
    public PLZ getPLZ() {
        return this.ort.getPLZ().get();
    }

    /**
     * Liefert den Ort.
     *
     * @return Ort
     */
    public Ort getOrt() {
        return this.ort;
    }

    /**
     * Liefert den Ortsnamen.
     *
     * @return Ortsname
     */
    public String getOrtsname() {
        return ort.getName();
    }

    /**
     * Zwei Postfaecher sind gleich, wenn sie die gleiche Attribute haben.
     *
     * @param obj das andere Postfach
     * @return true bei Gleichheit
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Postfach)) {
            return false;
        }
        Postfach other = (Postfach) obj;
        return this.nummer.equals(other.nummer) && this.ort.equals(other.ort);
    }

    /**
     * Da die PLZ meistens bereits ein Postfach adressiert, nehmen dies als
     * Basis fuer die Hashcode-Implementierung.
     *
     * @return hashCode
     */
    @Override
    public int hashCode() {
        return this.getOrt().hashCode();
    }

    /**
     * Hierueber wird das Postfach einzeilig ausgegeben.
     *
     * @return z.B. "Postfach 8 15, 09876 Nirwana"
     */
    @Override
    public String toString() {
        if (this.getNummer().isPresent()) {
            return "Postfach " + this.getNummerFormatted() + ", " + this.getOrt();
        } else {
            return this.getOrt().toString();
        }
    }

    /**
     * Liefert die einzelnen Attribute eines Postfaches als Map.
     *
     * @return Attribute als Map
     */
    @Override
    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("plz", getPLZ());
        map.put("ortsname", getOrtsname());
        if (nummer != null) {
            map.put("nummer", nummer);
        }
        return map;
    }

}