001
002package org.xbib.standardnumber;
003
004import java.util.regex.Matcher;
005import java.util.regex.Pattern;
006
007/**
008 * Zeitschriftendatenbank ID
009 *
010 *  ZDB is the world’s largest specialized database for serial titles (journals, annuals, newspapers, also
011 *  e-journals).
012 *
013 * @see <a href="http://support.d-nb.de/iltis/onlineRoutinen/Pruefziffernberechnung.htm"></a>Prüfziffernberechnung in ILTIS</a>
014 * @see <a href="https://wiki.dnb.de/pages/viewpage.action?pageId=48139522">DNB Wiki</a>
015 */
016public class ZDB extends AbstractStandardNumber implements Comparable<ZDB>, StandardNumber {
017
018    private final static Pattern PATTERN = Pattern.compile("\\b[\\p{Digit}]{2,10}\\-{0,1}[\\p{Digit}xX]{1}\\b");
019
020    private String value;
021
022    private String formatted;
023
024    private boolean createWithChecksum;
025
026    @Override
027    public String type() {
028        return "zdb";
029    }
030
031    @Override
032    public ZDB set(CharSequence value) {
033        this.value = value != null ? value.toString() : null;
034        return this;
035    }
036
037    @Override
038    public int compareTo(ZDB o) {
039        return value != null ?  value.compareTo(o.normalizedValue()) : -1;
040    }
041
042    @Override
043    public String normalizedValue() {
044        return value;
045    }
046
047    @Override
048    public ZDB createChecksum(boolean createWithChecksum) {
049        this.createWithChecksum = createWithChecksum;
050        return this;
051    }
052
053    @Override
054    public ZDB normalize() {
055        Matcher m = PATTERN.matcher(value);
056        if (m.find()) {
057            this.value = dehyphenate(value.substring(m.start(), m.end()));
058        }
059        return this;
060    }
061
062    @Override
063    public boolean isValid() {
064        return value != null && !value.isEmpty() && check();
065    }
066
067    @Override
068    public ZDB verify() throws NumberFormatException {
069        if (value == null || value.isEmpty()) {
070            throw new NumberFormatException("invalid");
071        }
072        if (!check()) {
073            throw new NumberFormatException("bad checksum");
074        }
075        return this;
076    }
077
078    @Override
079    public String format() {
080        if (formatted == null) {
081            StringBuilder sb = new StringBuilder(value);
082            this.formatted = sb.insert(sb.length()-1,"-").toString();
083        }
084        return formatted;
085    }
086
087    @Override
088    public ZDB reset() {
089        this.value = null;
090        this.formatted = null;
091        this.createWithChecksum = false;
092        return this;
093    }
094
095    private boolean check() {
096        int l = value.length() - 1;
097        int checksum = 0;
098        int weight = 2;
099        int val;
100        for (int i = l-1; i >= 0; i--) {
101            val = value.charAt(i) - '0';
102            checksum += val * weight++;
103        }
104        if (createWithChecksum) {
105            char ch = checksum % 11 == 10 ? 'X' : (char)('0' + (checksum % 11));
106            value = value.substring(0, l) + ch;
107        }
108        return checksum % 11 ==
109                (value.charAt(l) == 'X' || value.charAt(l) == 'x' ? 10 : value.charAt(l) - '0');
110    }
111
112    private String dehyphenate(String value) {
113        StringBuilder sb = new StringBuilder(value);
114        int i = sb.indexOf("-");
115        while (i >= 0) {
116            sb.deleteCharAt(i);
117            i = sb.indexOf("-");
118        }
119        return sb.toString();
120    }
121}