001
002package org.xbib.standardnumber;
003
004import java.util.regex.Matcher;
005import java.util.regex.Pattern;
006
007/**
008 * ISO 3297 International Standard Serial Number (ISSN)
009 *
010 * Z39.50 BIB-1 Use Attribute 8
011 *
012 * The International Standard Serial Number (ISSN) is a unique
013 * eight-digit number used to identify a print or electronic periodical
014 * publication. The ISSN system was adopted as international standard
015 * ISO 3297 in 1975. The ISO subcommittee TC 46/SC 9 is responsible
016 * for the standard.
017 *
018 * Quoted from http://www.issn.org/2-22636-All-about-ISSN.php
019 *
020 * The ISSN (International Standard Serial Number) is an eight-digit number
021 * which identifies periodical publications as such, including electronic
022 * serials.
023 *
024 * The ISSN is a numeric code which is used as an identifier: it has no
025 * signification in itself and does not contain in itself any information
026 * referring to the origin or contents of the publication.
027 *
028 * The ISSN takes the form of the acronym ISSN followed by two groups
029 * of four digits, separated by a hyphen. The eighth character is a
030 * control digit calculated according to a modulo 11 algorithm on
031 * the basis of the 7 preceding digits; this eighth control character
032 * may be an "X" if the result of the computing is equal to "10",
033 * in order to avoid any ambiguity.
034 *
035 *  The ISSN is linked to a standardized form of the title of the
036 *  identified serial, known as the "key title", which repeats
037 *  the title of the publication, qualifying it with additional elements
038 *  in order to distinguish it from other publications having identical
039 *  titles.
040 *
041 *  If the title of the publication changes in any significant way,
042 *  a new ISSN must be assigned in order to correspond to this new form
043 *  of title and avoid any confusion. A serial publication whose
044 *  title is modified several times in the course of its existence
045 *  will be assigned each time a new ISSN, thus allowing precise
046 *  identification of each form of the title : in fact it is then
047 *  considered that they are different publications even if there
048 *  is a logical link between them.
049 *
050 *  Contrary to other types of publications, the world of serial
051 *  publications is particularly changeable and complex :
052 *  the lifetime of a title may be extremely short; many publications
053 *  may be part of a complex set of relationships, etc.
054 *  These particularities themselves necessitated the introduction
055 *  of the ISSN.
056 *
057 */
058public class ISSN extends AbstractStandardNumber implements Comparable<ISSN>, StandardNumber {
059
060    private final static Pattern PATTERN = Pattern.compile("[0-9xX\\-]{8,9}");
061
062    private String value;
063
064    private String formatted;
065
066    private boolean createWithChecksum;
067
068    @Override
069    public String type() {
070        return "issn";
071    }
072
073    @Override
074    public int compareTo(ISSN issn) {
075        return value != null ? normalizedValue().compareTo(issn.normalizedValue()) : -1;
076    }
077
078    @Override
079    public ISSN set(CharSequence value) {
080        this.value = value != null ? value.toString() : null;
081        return this;
082    }
083
084    @Override
085    public ISSN createChecksum(boolean createWithChecksum) {
086        this.createWithChecksum = createWithChecksum;
087        return this;
088    }
089
090
091    @Override
092    public ISSN normalize() {
093        Matcher m = PATTERN.matcher(value);
094        if (m.find() && value.length() >= m.end()) {
095            this.value = dehyphenate(value.substring(m.start(), m.end()));
096        }
097        return this;
098    }
099
100    @Override
101    public boolean isValid() {
102        return value != null && !value.isEmpty() && check();
103    }
104
105    @Override
106    public ISSN verify() throws NumberFormatException {
107        if (value == null || value.isEmpty()) {
108            throw new NumberFormatException("invalid");
109        }
110        if (!check()) {
111            throw new NumberFormatException("bad createChecksum");
112        }
113        return this;
114    }
115
116    /**
117     * Returns the value representation of the standard number
118     * @return value
119     */
120    @Override
121    public String normalizedValue() {
122        return value;
123    }
124
125    /**
126     * Format this number
127     *
128     * @return the formatted number
129     */
130    @Override
131    public String format() {
132        return formatted;
133    }
134
135    @Override
136    public ISSN reset() {
137        this.value = null;
138        this.formatted = null;
139        this.createWithChecksum = false;
140        return this;
141    }
142
143    public GTIN toGTIN() throws NumberFormatException {
144        return new GTIN().set("977" + value.substring(0, 7) + "000").createChecksum(true).normalize().verify();
145    }
146
147    public GTIN toGTIN(String additionalCode) throws NumberFormatException {
148        // "977" + ISSN + add-on + placeholder for createChecksum
149        return new GTIN().set("977" + value.substring(0, 7) + additionalCode + "0").createChecksum(true).normalize().verify();
150    }
151
152    private boolean check() {
153        int l = createWithChecksum ? value.length() : value.length() - 1;
154        int checksum = 0;
155        int weight;
156        int val;
157        for (int i = 0; i < l; i++) {
158            val = value.charAt(i) - '0';
159            weight = 8 - i;
160            checksum += weight * val;
161        }
162        int chk = checksum % 11;
163        char p = chk == 0 ? '0' : chk == 1 ? 'X' : (char)((11-chk) + '0');
164        return p == Character.toUpperCase(value.charAt(l));
165    }
166
167    private String dehyphenate(String isbn) {
168        StringBuilder sb = new StringBuilder(isbn);
169        int i = sb.indexOf("-");
170        while (i > 0) {
171            sb.deleteCharAt(i);
172            i = sb.indexOf("-");
173        }
174        if (sb.length() > 7) {
175            this.formatted = sb.substring(0, 4) + "-" + sb.substring(4, 8);
176        }
177        return sb.toString();
178    }
179
180}