001package org.xbib.standardnumber;
002
003import java.util.regex.Matcher;
004import java.util.regex.Pattern;
005
006/**
007 * Global Trade Item Number (GTIN)
008 *
009 * GTIN describes a family of GS1 (EAN.UCC) global data structures that employ
010 * 14 digits and can be encoded into various types of data carriers.
011 *
012 * Currently, GTIN is used exclusively within bar codes, but it could also be used
013 * in other data carriers such as radio frequency identification (RFID).
014 * The GTIN is only a term and does not impact any existing standards, nor does
015 * it place any additional requirements on scanning hardware.
016 *
017 * For North American companies, the UPC is an existing form of the GTIN.
018 *
019 * Since 2005, EAN International and American UCC merged to GS1 and also
020 * EAN and UPC is now named GTIN.
021 *
022 * The EAN/UCC-13 code is now officially called GTIN-13 (Global Trade Identifier Number).
023 * Former 12-digit UPC codes can be converted into EAN/UCC-13 code by simply
024 * adding a preceeding zero.
025 *
026 * As of January 1, 2007, the former ISBN numbers have been integrated into
027 * the EAN/UCC-13 code. For each old ISBN-10 code, there exists a proper translation
028 * into EAN/UCC-13 by adding "978" as prefix.
029 *
030 * The family of data structures comprising GTIN include:
031 *
032 * GTIN-8 (EAN/UCC-8): this is an 8-digit number
033 * GTIN-12 (UPC-A): this is a 12-digit number
034 * GTIN-13 (EAN/UCC-13): this is a 13-digit number
035 * GTIN-14 (EAN/UCC-14 or ITF-14): this is a 14-digit number
036 *
037 * @see <a href="http://www.gtin.info/">GTIN info</a>
038 */
039public class GTIN extends AbstractStandardNumber implements Comparable<GTIN>, StandardNumber {
040
041    private static final Pattern PATTERN = Pattern.compile("\\b[\\p{Digit}\\-]{3,18}\\b");
042
043    private String value;
044
045    private boolean createWithChecksum;
046
047    @Override
048    public String type() {
049        return "gtin";
050    }
051
052    @Override
053    public int compareTo(GTIN gtin) {
054        return gtin != null ? normalizedValue().compareTo(gtin.normalizedValue()) : -1;
055    }
056
057    @Override
058    public GTIN set(CharSequence value) {
059        this.value = value != null ? value.toString() : null;
060        return this;
061    }
062
063    @Override
064    public GTIN createChecksum(boolean createWithChecksum) {
065        this.createWithChecksum = createWithChecksum;
066        return this;
067    }
068
069    @Override
070    public GTIN normalize() {
071        Matcher m = PATTERN.matcher(value);
072        if (m.find() && value.length() >= m.end()) {
073            this.value = dehyphenate(value.substring(m.start(), m.end()));
074        }
075        return this;
076    }
077
078    @Override
079    public boolean isValid() {
080        return value != null && !value.isEmpty() && check();
081    }
082
083    @Override
084    public GTIN verify() throws NumberFormatException {
085        if (value == null || value.isEmpty()) {
086            throw new NumberFormatException("invalid");
087        }
088        if (!check()) {
089            throw new NumberFormatException("bad checksum");
090        }
091        return this;
092    }
093
094    @Override
095    public String normalizedValue() {
096        return value;
097    }
098
099    @Override
100    public String format() {
101        return value;
102    }
103
104    @Override
105    public GTIN reset() {
106        this.value = null;
107        this.createWithChecksum = false;
108        return this;
109    }
110
111    private boolean check() {
112        int l = value.length() - 1;
113        int checksum = 0;
114        int weight;
115        int val;
116        for (int i = 0; i < l; i++) {
117            val = value.charAt(i) - '0';
118            weight = i % 2 == 0 ? 1 : 3;
119            checksum += val * weight;
120        }
121        int chk = 10 - checksum % 10;
122        if (createWithChecksum) {
123            char ch = (char)('0' + chk);
124            value = value.substring(0, l) + ch;
125        }
126        return chk == (value.charAt(l) - '0');
127    }
128
129    private String dehyphenate(String isbn) {
130        StringBuilder sb = new StringBuilder(isbn);
131        int i = sb.indexOf("-");
132        while (i >= 0) {
133            sb.deleteCharAt(i);
134            i = sb.indexOf("-");
135        }
136        return sb.toString();
137    }
138}