001package org.xbib.standardnumber;
002
003import org.xbib.standardnumber.check.iso7064.MOD3736;
004
005import java.util.regex.Matcher;
006import java.util.regex.Pattern;
007
008/**
009 * ISO 15706 International Standard Audiovisual Number (ISAN)
010 *
011 * The International Standard Audiovisual Number (ISAN) is a unique identifier for
012 * audiovisual works and related versions, similar to ISBN for books.
013 *
014 * It was developed within an ISO (International Organisation for Standardisation) TC46/SC9
015 * working group. ISAN is managed and run by ISAN-IA.
016 *
017 * The ISAN standard (ISO standard 15706:2002 & ISO 15706-2) is recommended or required
018 * as the audiovisual identifier of choice for producers, studios, broadcasters,
019 * Internet media providers and video games publishers who need to encode, track, and
020 * distribute video in a variety of formats.
021 *
022 * It provides a unique, internationally recognized and permanent reference number for each
023 * audiovisual work and related versions registered in the ISAN system.
024 *
025 * ISAN identifies works throughout their entire life cycle from conception, to production,
026 * to distribution and consumption.
027 *
028 * ISANs can be incorporated in both digital and physical media, such as theatrical
029 * release prints, DVDs, publications, advertising, marketing materials and packaging,
030 * as well as licensing contracts to uniquely identify works.
031 *
032 * The ISAN identifier is incorporated in many draft and final standards such as
033 * AACS, DCI, MPEG, DVB, and ATSC.
034 */
035public class ISAN extends AbstractStandardNumber implements Comparable<ISAN>, StandardNumber {
036
037    private static final Pattern PATTERN = Pattern.compile("[\\p{Alnum}\\-]{16,34}");
038
039    private String value;
040
041    private String formatted;
042
043    private boolean versioned;
044
045    @Override
046    public String type() {
047        return "isan";
048    }
049
050    @Override
051    public int compareTo(ISAN isan) {
052        return isan != null ? normalizedValue().compareTo(isan.normalizedValue()) : -1;
053    }
054
055    @Override
056    public ISAN set(CharSequence value) {
057        this.value = value != null ? value.toString() : null;
058        return this;
059    }
060
061    @Override
062    public ISAN createChecksum(boolean createWithChecksum) {
063        return this;
064    }
065
066    @Override
067    public ISAN normalize() {
068        Matcher m = PATTERN.matcher(value);
069        if (m.find()) {
070            this.value = clean(value.substring(m.start(), m.end()));
071            versioned = value.length() > 17;
072        }
073        return this;
074    }
075
076    @Override
077    public boolean isValid() {
078        return value != null && !value.isEmpty() && check();
079    }
080
081    @Override
082    public ISAN verify() throws NumberFormatException {
083        if (value == null) {
084            throw new NumberFormatException();
085        }
086        if (!check()) {
087            throw new NumberFormatException("invalid checksum");
088        }
089        return this;
090    }
091
092    @Override
093    public String normalizedValue() {
094        return value;
095    }
096
097    @Override
098    public String format() {
099        return formatted;
100    }
101
102    public ISAN versioned() {
103        this.versioned = true;
104        return this;
105    }
106
107    @Override
108    public ISAN reset() {
109        this.value = null;
110        this.formatted = null;
111        this.versioned = false;
112        return this;
113    }
114
115    private final static MOD3736 check = new MOD3736();
116
117    private boolean check() {
118        if (versioned) {
119            int chk1 = value.length() >= 17 ? check.compute(value.substring(0,17)) : -1;
120            int chk2 = value.length() >= 26 ? check.compute(value.substring(0,16) + value.substring(17,26)) : -1;
121            if (chk1 != 1) {
122                return false;
123            }
124            if (chk2 != 1) {
125                return false;
126            }
127        } else {
128            int chk = check.compute(value);
129            if (chk != 1) {
130                return false;
131            }
132        }
133        return true;
134    }
135
136    private String clean(String value) {
137        StringBuilder sb = new StringBuilder(value);
138        int i = sb.indexOf("-");
139        while (i >= 0) {
140            sb.deleteCharAt(i);
141            i = sb.indexOf("-");
142        }
143        i = sb.indexOf(" ");
144        while (i >= 0) {
145            sb.deleteCharAt(i);
146            i = sb.indexOf(" ");
147        }
148        this.formatted = "ISAN "
149                + (sb.length() < 4 ? sb :
150                    sb.substring(0,4) + "-"
151                + (sb.length() < 8 ? sb.substring(4) :
152                    sb.substring(4,8) + "-"
153                + (sb.length() < 12 ? sb.substring(8) :
154                    sb.substring(8,12) + "-"
155                + (sb.length() < 16 ? sb.substring(12) :
156                    sb.substring(12,16) + "-"
157                + (sb.length() < 17 ? sb.substring(16) :
158                    sb.substring(16,17))))));
159        if (sb.length() > 17) {
160            this.formatted = this.formatted + "-"
161                    + (sb.length() < 21 ? sb.substring(17) :
162                      (sb.substring(17, 21) + "-"
163                    + (sb.length() < 25 ? sb.substring(21) :
164                      (sb.substring(21, 25)  + "-"
165                    + (sb.length() < 26 ? sb.substring(25) :
166                      sb.substring(25, 26))))));
167        }
168        return sb.toString();
169    }
170}