001package org.xbib.standardnumber;
002
003import java.util.regex.Matcher;
004import java.util.regex.Pattern;
005
006/**
007 *  Z39.56 Serial Item and Contribution Identifier
008 *
009 * The SICI code (Serial Item and Contribution Identifier) is described in the
010 * American standard ANSI/NISO Z39.56. The SICI code is known among
011 * international scientific publishers and reproduction rights agencies.
012 * The SICI even provides for the unambiguous identification of each article
013 * or contribution published in a given issue of a serial publication.
014 *
015 * The SICI contains:
016 *
017 * - the ISSN
018 *
019 * - the date of publication, between brackets and formatted according to the
020 *   formula YYYYMM
021 *
022 * - the issue number
023 *
024 * - the version number of the standard, here 1, preceded by a semicolon
025 *
026 * - and lastly a hyphen which precedes the control character calculated
027 *   on the basis of all the preceding characters
028 *
029 *  Example:
030 *  0095-4403(199502/03)21:3<12:WATIIB>2.0.TX;2-J
031 *
032 */
033public class SICI extends AbstractStandardNumber implements Comparable<SICI>, StandardNumber {
034
035    private static final Pattern PATTERN = Pattern.compile("[\\p{Graph}\\p{Punct}]{12,64}");
036
037    private String value;
038
039    private String formatted;
040
041    private boolean createWithChecksum;
042
043    @Override
044    public String type() {
045        return "sici";
046    }
047
048    @Override
049    public int compareTo(SICI sici) {
050        return sici != null ? normalizedValue().compareTo(sici.normalizedValue()) : -1;
051    }
052
053    @Override
054    public SICI set(CharSequence value) {
055        this.value = value != null ? value.toString() : null;
056        return this;
057    }
058
059    @Override
060    public SICI createChecksum(boolean createWithChecksum) {
061        this.createWithChecksum = createWithChecksum;
062        return this;
063    }
064
065    @Override
066    public SICI normalize() {
067        Matcher m = PATTERN.matcher(value);
068        if (m.find()) {
069            this.value = clean(value.substring(m.start(), m.end()));
070        }
071        return this;
072    }
073
074    @Override
075    public boolean isValid() {
076        return value != null && !value.isEmpty() && check();
077    }
078
079    @Override
080    public SICI verify() throws NumberFormatException {
081        if (value == null) {
082            throw new NumberFormatException("invalid");
083        }
084        if (!check()) {
085            throw new NumberFormatException("bad checksum");
086        }
087        return this;
088    }
089
090    @Override
091    public String normalizedValue() {
092        return value;
093    }
094
095    @Override
096    public String format() {
097        return formatted;
098    }
099
100    @Override
101    public SICI reset() {
102        this.value = null;
103        this.formatted = null;
104        this.createWithChecksum = false;
105        return this;
106    }
107
108    private final static String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ#";
109
110    private final static int modulus = ALPHABET.length();
111
112    private boolean check() {
113        int l = value.length() - 1;
114        int val;
115        int sum = 0;
116        for (int i = 0; i < l; i++) {
117            val = ALPHABET.indexOf(value.charAt(i));
118            sum += val *  (i %2 == 0 ? 1 : 3);
119        }
120        int chk = modulus - sum % modulus;
121        if (createWithChecksum) {
122            char ch = chk > 35 ? '#' : chk > 9 ? (char)(10 + (chk - 'A')) : (char)('0' + chk);
123            value = value.substring(0, l) + ch;
124        }
125        char digit = value.charAt(l);
126        int chk2 = digit == '#' ? 36 : (digit >= '0' && digit <= '9') ? digit - '0' : digit -'A' + 10;
127        return chk == chk2;
128    }
129
130    private String clean(String raw) {
131        if (raw == null) {
132            return null;
133        }
134        StringBuilder sb = new StringBuilder(raw);
135        int pos = sb.indexOf("SICI ");
136        if (pos >= 0) {
137            sb = new StringBuilder(sb.substring(pos + 5));
138        }
139        this.formatted = "SICI " + sb;
140        return sb.toString();
141    }
142}