001package org.xbib.standardnumber;
002
003import java.util.regex.Matcher;
004import java.util.regex.Pattern;
005
006/**
007 *  European Article Number is a 13-digit barcoding standard for marking products
008 *  sold at retail point of sale.
009 *
010 *  Numbers encoded in UPC and EAN barcodes are known as
011 *  Global Trade Item Numbers (GTIN).
012 */
013public class EAN extends AbstractStandardNumber implements Comparable<EAN>, StandardNumber {
014
015    private static final Pattern PATTERN = Pattern.compile("\\b[\\p{Digit}\\s]{13,18}\\b");
016
017    private String value;
018
019    private boolean createWithChecksum;
020
021    @Override
022    public String type() {
023        return "ean";
024    }
025
026    @Override
027    public int compareTo(EAN ean) {
028        return ean != null ? normalizedValue().compareTo(ean.normalizedValue()) : -1;
029    }
030
031    @Override
032    public EAN set(CharSequence value) {
033        this.value = value != null ? value.toString() : null;
034        return this;
035    }
036
037    @Override
038    public EAN createChecksum(boolean createWithChecksum) {
039        this.createWithChecksum = createWithChecksum;
040        return this;
041    }
042
043    @Override
044    public EAN normalize() {
045        Matcher m = PATTERN.matcher(value);
046        if (m.find()) {
047            this.value = despace(value.substring(m.start(), m.end()));
048        }
049        return this;
050    }
051
052    @Override
053    public boolean isValid() {
054        return value != null && !value.isEmpty() && check();
055    }
056
057    @Override
058    public EAN verify() throws NumberFormatException {
059        if (value == null || value.isEmpty()) {
060            throw new NumberFormatException("invalid");
061        }
062        if (!check()) {
063            throw new NumberFormatException("bad checkum");
064        }
065        return this;
066    }
067
068    @Override
069    public String normalizedValue() {
070        return value;
071    }
072
073    @Override
074    public String format() {
075        return value;
076    }
077
078    public EAN reset() {
079        this.value = null;
080        this.createWithChecksum = false;
081        return this;
082    }
083
084    private boolean check() {
085        int l = value.length() - 1;
086        int checksum = 0;
087        int weight;
088        int val;
089        for (int i = 0; i < l; i++) {
090            val = value.charAt(i) - '0';
091            weight = i % 2 == 0 ? 1 : 3;
092            checksum += val * weight;
093        }
094        int chk = 10 - checksum % 10;
095        if (createWithChecksum) {
096            char ch = (char)('0' + chk);
097            value = value.substring(0, l) + ch;
098        }
099        return chk == (value.charAt(l) - '0');
100    }
101
102    private String despace(String isbn) {
103        StringBuilder sb = new StringBuilder(isbn);
104        int i = sb.indexOf(" ");
105        while (i >= 0) {
106            sb.deleteCharAt(i);
107            i = sb.indexOf(" ");
108        }
109        return sb.toString();
110    }
111}