001package org.xbib.standardnumber;
002
003import java.util.regex.Matcher;
004import java.util.regex.Pattern;
005
006/**
007 * ISO 15420 Universal Product Code (UPC)
008 *
009 * The Universal Product Code (UPC) is a barcode symbology (i.e., a specific type of barcode)
010 * that is widely used in the United States, Canada, the United Kingdom, Australia,
011 * New Zealand and in other countries for tracking trade items in stores.
012 * Its most common form, the UPC-A, consists of 12 numerical digits, which are uniquely
013 * assigned to each trade item.
014 *
015 * Along with the related EAN barcode, the UPC is the barcode mainly used for scanning
016 * of trade items at the point of sale, per GS1 specifications.
017 *
018 * UPC data structures are a component of GTINs (Global Trade Item Numbers).
019 *
020 * All of these data structures follow the global GS1 specification which bases on
021 * international standards.
022 *
023 */
024public class UPC extends AbstractStandardNumber implements Comparable<UPC>, StandardNumber {
025
026    private static final Pattern PATTERN = Pattern.compile("[\\p{Digit}]{0,12}");
027
028    private String value;
029
030    private boolean createWithChecksum;
031
032    @Override
033    public String type() {
034        return "upc";
035    }
036
037    @Override
038    public int compareTo(UPC upc) {
039        return upc != null ? normalizedValue().compareTo(upc.normalizedValue()) : -1;
040    }
041
042    @Override
043    public UPC set(CharSequence value) {
044        this.value = value != null ? value.toString() : null;
045        return this;
046    }
047
048    @Override
049    public UPC createChecksum(boolean createWithChecksum) {
050        this.createWithChecksum = createWithChecksum;
051        return this;
052    }
053
054    @Override
055    public UPC normalize() {
056        Matcher m = PATTERN.matcher(value);
057        if (m.find()) {
058            this.value = value.substring(m.start(), m.end());
059        }
060        return this;
061    }
062
063    @Override
064    public boolean isValid() {
065        return value != null && !value.isEmpty() && check();
066    }
067
068    @Override
069    public UPC verify() throws NumberFormatException {
070        if (value == null || value.isEmpty()) {
071            throw new NumberFormatException("invalid");
072        }
073        if (!check()) {
074            throw new NumberFormatException("bad checksum");
075        }
076        return this;
077    }
078
079    @Override
080    public String normalizedValue() {
081        return value;
082    }
083
084    @Override
085    public String format() {
086        return value;
087    }
088
089    @Override
090    public UPC reset() {
091        this.value = null;
092        this.createWithChecksum = false;
093        return this;
094    }
095
096    private boolean check() {
097        int l = value.length() - 1;
098        int checksum = 0;
099        int weight;
100        int val;
101        for (int i = 0; i < l; i++) {
102            val = value.charAt(i) - '0';
103            weight = i%2 == 0 ? 3 : 1;
104            checksum += val * weight;
105        }
106        int chk = 10 - checksum % 10;
107        if (createWithChecksum) {
108            char ch = (char)('0' + chk);
109            value = value.substring(0, l) + ch;
110        }
111        return chk == (value.charAt(l) - '0');
112    }
113}