001package org.xbib.standardnumber;
002
003import org.xbib.standardnumber.check.iso7064.MOD112;
004
005import java.util.regex.Matcher;
006import java.util.regex.Pattern;
007
008/**
009 * ISO 27729 International Standard Name Identifier (ISNI)
010 *
011 * The International Standard Name Identifier (ISNI) is a method for uniquely identifying
012 * the public identities of contributors to media content such as books, TV programmes,
013 * and newspaper articles. Such an identifier consists of 16 numerical digits divided
014 * into four blocks.
015 *
016 * Checksum is in accordance to ISO/IEC 7064:2003, MOD 11-2
017 */
018public class ISNI extends AbstractStandardNumber implements Comparable<ISNI>, StandardNumber {
019
020    private final static Pattern PATTERN = Pattern.compile("[\\p{Digit}xX\\-\\s]{16,24}");
021
022    private String value;
023
024    private String formatted;
025
026    private boolean createWithChecksum;
027
028    @Override
029    public String type() {
030        return "isni";
031    }
032
033    /**
034     * Creates a new ISNI
035     *
036     * @param value the value
037     */
038    @Override
039    public ISNI set(CharSequence value) {
040        this.value = value != null ? value.toString() : null;
041        return this;
042    }
043
044    @Override
045    public ISNI createChecksum(boolean createWithChecksum) {
046        this.createWithChecksum = createWithChecksum;
047        return this;
048    }
049
050    @Override
051    public int compareTo(ISNI isni) {
052        return value != null ? value.compareTo((isni).normalizedValue()) : -1;
053    }
054
055    @Override
056    public boolean isValid() {
057        return value != null && !value.isEmpty() && check();
058    }
059
060    @Override
061    public ISNI verify() throws NumberFormatException {
062        if (!check()) {
063            throw new NumberFormatException("bad createChecksum");
064        }
065        return this;
066    }
067
068    /**
069     * Returns the value representation of the standard number
070     * @return value
071     */
072    @Override
073    public String normalizedValue() {
074        return value;
075    }
076
077    /**
078     * Format this number
079     *
080     * @return the formatted number
081     */
082    @Override
083    public String format() {
084        if (formatted == null) {
085            this.formatted = value;
086        }
087        return formatted;
088    }
089
090    @Override
091    public ISNI normalize() {
092        Matcher m = PATTERN.matcher(value);
093        if (m.find()) {
094            this.value = clean(value.substring(m.start(), m.end()));
095        }
096        return this;
097    }
098
099    @Override
100    public ISNI reset() {
101        this.value = null;
102        this.formatted = null;
103        this.createWithChecksum = false;
104        return this;
105    }
106
107    private final static MOD112 check = new MOD112();
108
109    private boolean check() {
110        if (createWithChecksum) {
111            this.value = check.encode(value.length() < 16 ? value : value.substring(0, value.length()-1));
112        }
113        if (value.length() < 16) {
114            return false;
115        }
116        return check.verify(value);
117    }
118
119    private String clean(String isbn) {
120        StringBuilder sb = new StringBuilder(isbn);
121        int i = sb.indexOf("-");
122        while (i >= 0) {
123            sb.deleteCharAt(i);
124            i = sb.indexOf("-");
125        }
126        i = sb.indexOf(" ");
127        while (i >= 0) {
128            sb.deleteCharAt(i);
129            i = sb.indexOf(" ");
130        }
131        return sb.toString();
132    }
133
134}