001package org.xbib.standardnumber;
002
003import java.util.regex.Matcher;
004import java.util.regex.Pattern;
005
006/**
007 * ISO 21047 International Standard Text Code (ISTC)
008 *
009 * The International Standard Text Code (ISTC) is a numbering system for the unique identification
010 * of text-based works; the term “work” can refer to any content appearing in conventional
011 * printed books, audio-books, static e-books or enhanced digital books, as well as content
012 * which might appear in a newspaper or journal.
013 *
014 * The ISTC provides sales analysis systems, retail websites, library catalogs and other
015 * bibliographic systems with a method of automatically linking together publications
016 * of the “same content” and/or “related content”, thus improving discoverability of
017 * products and efficiencies.
018 *
019 * An ISTC number is the link between a user’s search for a piece of content and the
020 * ultimate sale or loan of a publication.
021 *
022 * The standard was formally published in March 2009
023 *
024 * Checksum algorithm is ISO 7064 MOD 16/3
025 *
026 */
027public class ISTC extends AbstractStandardNumber implements Comparable<ISTC>, StandardNumber {
028
029    private static final Pattern PATTERN = Pattern.compile("[\\p{Alnum}\\-\\s]{12,24}");
030
031    private String value;
032
033    private String formatted;
034
035    private boolean createWithChecksum;
036
037    @Override
038    public String type() {
039        return "istc";
040    }
041
042    @Override
043    public int compareTo(ISTC istc) {
044        return istc != null ? normalizedValue().compareTo(istc.normalizedValue()) : -1;
045    }
046
047    @Override
048    public ISTC set(CharSequence value) {
049        this.value = value != null ? value.toString() : null;
050        return this;
051    }
052
053    @Override
054    public ISTC createChecksum(boolean createWithChecksum) {
055        this.createWithChecksum = createWithChecksum;
056        return this;
057    }
058
059    @Override
060    public ISTC normalize() {
061        Matcher m = PATTERN.matcher(value);
062        if (m.find()) {
063            this.value = clean(value.substring(m.start(), m.end()));
064        }
065        return this;
066    }
067
068    @Override
069    public boolean isValid() {
070        return value != null && !value.isEmpty() && check();
071    }
072
073    @Override
074    public ISTC verify() throws NumberFormatException {
075        if (value == null || value.isEmpty()) {
076            throw new NumberFormatException("invalid");
077        }
078        if (!check()) {
079            throw new NumberFormatException("bad createChecksum");
080        }
081        return this;
082    }
083
084    @Override
085    public String normalizedValue() {
086        return value;
087    }
088
089    @Override
090    public String format() {
091        return formatted;
092    }
093
094    @Override
095    public ISTC reset() {
096        this.value = null;
097        this.formatted = null;
098        this.createWithChecksum = false;
099        return this;
100    }
101
102    private boolean check() {
103        int l = value.length() - 1;
104        int checksum = 0;
105        int weight;
106        int factor;
107        int val;
108        for (int i = 0; i < l; i++) {
109            val = value.charAt(i);
110            if (val >= 'A' && val <= 'Z') {
111                val = 10 + (val - 'A');
112            }
113            if (val >= '0' && val <= '9') {
114                val = val - '0';
115            }
116            factor = i % 4 < 2 ? 1 : 5;
117            weight = (12 - 2 * (i % 4)) - factor; // --> 11,9,3,1
118            checksum += val * weight;
119        }
120        int chk = checksum % 16;
121        if (createWithChecksum) {
122            char ch = 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 >= '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 i = sb.indexOf("-");
136        while (i >= 0) {
137            sb.deleteCharAt(i);
138            i = sb.indexOf("-");
139        }
140        i = sb.indexOf(" ");
141        while (i >= 0) {
142            sb.deleteCharAt(i);
143            i = sb.indexOf(" ");
144        }
145        if (sb.indexOf("ISTC") == 0) {
146            sb = new StringBuilder(sb.substring(4));
147        }
148        if (sb.length() > 15) {
149            this.formatted = "ISTC "
150                + sb.substring(0,3) + "-"
151                + sb.substring(3,7) + "-"
152                + sb.substring(7,15) + "-"
153                + sb.substring(15);
154        }
155        return sb.toString();
156    }
157}