/*
* Copyright (C) 2014 Jörg Prante
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses
* or write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*
* The interactive user interfaces in modified source and object code
* versions of this program must display Appropriate Legal Notices,
* as required under Section 5 of the GNU Affero General Public License.
*
*/
package org.xbib.standardnumber;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Z39.56 Serial Item and Contribution Identifier
*
* The SICI code (Serial Item and Contribution Identifier) is described in the
* American standard ANSI/NISO Z39.56. The SICI code is known among
* international scientific publishers and reproduction rights agencies.
* The SICI even provides for the unambiguous identification of each article
* or contribution published in a given issue of a serial publication.
*
* The SICI contains:
*
* - the ISSN
*
* - the date of publication, between brackets and formatted according to the
* formula YYYYMM
*
* - the issue number
*
* - the version number of the standard, here 1, preceded by a semicolon
*
* - and lastly a hyphen which precedes the control character calculated
* on the basis of all the preceding characters
*
* Example:
* 0095-4403(199502/03)21:3<12:WATIIB>2.0.TX;2-J
*
*/
public class SICI extends AbstractStandardNumber implements Comparable<SICI>, StandardNumber {
private static final Pattern PATTERN = Pattern.compile("[\\p{Graph}\\p{Punct}]{12,64}");
private String value;
private String formatted;
private boolean createWithChecksum;
@Override
public String type() {
return "sici";
}
@Override
public int compareTo(SICI sici) {
return sici != null ? normalizedValue().compareTo(sici.normalizedValue()) : -1;
}
@Override
public SICI set(CharSequence value) {
this.value = value != null ? value.toString() : null;
return this;
}
@Override
public SICI createChecksum(boolean createWithChecksum) {
this.createWithChecksum = createWithChecksum;
return this;
}
@Override
public SICI normalize() {
Matcher m = PATTERN.matcher(value);
if (m.find()) {
this.value = clean(value.substring(m.start(), m.end()));
}
return this;
}
@Override
public boolean isValid() {
return value != null && !value.isEmpty() && check();
}
@Override
public SICI verify() throws NumberFormatException {
if (value == null) {
throw new NumberFormatException("invalid");
}
if (!check()) {
throw new NumberFormatException("bad checksum");
}
return this;
}
@Override
public String normalizedValue() {
return value;
}
@Override
public String format() {
return formatted;
}
@Override
public SICI reset() {
this.value = null;
this.formatted = null;
this.createWithChecksum = false;
return this;
}
private final static String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ#";
private final static int modulus = ALPHABET.length();
private boolean check() {
int l = value.length() - 1;
int val;
int sum = 0;
for (int i = 0; i < l; i++) {
val = ALPHABET.indexOf(value.charAt(i));
sum += val * (i %2 == 0 ? 1 : 3);
}
int chk = modulus - sum % modulus;
if (createWithChecksum) {
char ch = chk > 35 ? '#' : chk > 9 ? (char)(10 + (chk - 'A')) : (char)('0' + chk);
value = value.substring(0, l) + ch;
}
char digit = value.charAt(l);
int chk2 = digit == '#' ? 36 : (digit >= '0' && digit <= '9') ? digit - '0' : digit -'A' + 10;
return chk == chk2;
}
private String clean(String raw) {
if (raw == null) {
return null;
}
StringBuilder sb = new StringBuilder(raw);
int pos = sb.indexOf("SICI ");
if (pos >= 0) {
sb = new StringBuilder(sb.substring(pos + 5));
}
this.formatted = "SICI " + sb;
return sb.toString();
}
}