001package org.xbib.standardnumber;
002
003
004import java.util.regex.Matcher;
005import java.util.regex.Pattern;
006
007/**
008 * Pica Productie Nummer
009 *
010 * A catalog record numbering system, uniquely identifying records, used by PICA
011 * (Project voor geIntegreerde Catalogus Automatisering) integrated library systems.
012 *
013 */
014public class PPN extends AbstractStandardNumber implements Comparable<PPN>, StandardNumber {
015
016    private final static Pattern PATTERN = Pattern.compile("[\\p{Digit}]{3,10}\\-{0,1}[\\p{Digit}xX]{1}\\b");
017
018    private String value;
019
020    private String formatted;
021
022    private boolean createWithChecksum;
023
024    @Override
025    public String type() {
026        return "ppn";
027    }
028
029    @Override
030    public PPN set(CharSequence value) {
031        this.value = value != null ? value.toString() : null;
032        return this;
033    }
034
035    @Override
036    public int compareTo(PPN o) {
037        return value != null ? normalizedValue().compareTo(o.normalizedValue()) : -1;
038    }
039
040    @Override
041    public String normalizedValue() {
042        return value;
043    }
044
045    @Override
046    public PPN createChecksum(boolean createWithChecksum) {
047        this.createWithChecksum = createWithChecksum;
048        return this;
049    }
050
051    @Override
052    public PPN normalize() {
053        Matcher m = PATTERN.matcher(value);
054        if (m.find()) {
055            this.value = dehyphenate(value.substring(m.start(), m.end()));
056        }
057        return this;
058    }
059
060    @Override
061    public boolean isValid() {
062        return value != null && !value.isEmpty() && check();
063    }
064
065    @Override
066    public PPN verify() throws NumberFormatException {
067        if (value == null || value.isEmpty()) {
068            throw new NumberFormatException("invalid");
069        }
070        if (!check()) {
071            throw new NumberFormatException("bad checksum");
072        }
073        return this;
074    }
075
076    @Override
077    public String format() {
078        if (formatted == null) {
079            StringBuilder sb = new StringBuilder(value);
080            this.formatted = sb.insert(sb.length()-1,"-").toString();
081        }
082        return formatted;
083    }
084
085    @Override
086    public PPN reset() {
087        this.value = null;
088        this.formatted = null;
089        this.createWithChecksum = false;
090        return this;
091    }
092
093    private boolean check() {
094        int checksum = 0;
095        int weight = 2;
096        int val;
097        int l = value.length() - 1;
098        for (int i = l - 1; i >= 0; i--) {
099            val = value.charAt(i) - '0';
100            checksum += val * weight++;
101        }
102        if (createWithChecksum) {
103            char ch = checksum % 11 == 10 ? 'X' : (char)('0' + (checksum % 11));
104            value = value.substring(0, l) + ch;
105        }
106        return 11 - checksum % 11 ==
107                (value.charAt(l) == 'X' || value.charAt(l) == 'x' ? 10 : value.charAt(l) - '0');
108    }
109
110    private String dehyphenate(String value) {
111        StringBuilder sb = new StringBuilder(value);
112        int i = sb.indexOf("-");
113        while (i >= 0) {
114            sb.deleteCharAt(i);
115            i = sb.indexOf("-");
116        }
117        return sb.toString();
118    }
119}