001package org.xbib.standardnumber; 002 003import java.util.regex.Matcher; 004import java.util.regex.Pattern; 005 006/** 007 * ISO 10957 International Standard Music Number (ISMN) 008 * 009 * Z39.50 BIB-1 Use Attribute 1092 010 * 011 * The International Standard Music Number (ISMN) is a thirteen-character alphanumeric identifier 012 * for printed music developed by ISO. The original proposal for an ISMN was made by the 013 * UK Branch of IAML (International Association of Music Libraries, Archives and Documentation 014 * Centres). 015 * 016 * The original format comprised four elements: a distinguishing prefix M, a publisher ID, 017 * an item ID and a check digit, typically looking like M-2306-7118-7. 018 * 019 * From 1 January 2008 the ISMN was defined as a thirteen digit identifier beginning 979-0 where 020 * the zero replaced M in the old-style number. The resulting number is identical with its 021 * EAN-13 number as encoded in the item's barcode. 022 * 023 * @see <a href="http://www.ismn-international.org/download/Web_ISMN%20Manual_2008-3.pdf">ISMN Manual 2008</a> 024 */ 025public class ISMN extends AbstractStandardNumber implements Comparable<ISMN>, StandardNumber { 026 027 private static final Pattern PATTERN = Pattern.compile("[\\p{Digit}M\\-]{0,17}"); 028 029 private String value; 030 031 private boolean createWithChecksum; 032 033 @Override 034 public String type() { 035 return "ismn"; 036 } 037 038 @Override 039 public int compareTo(ISMN ismn) { 040 return ismn != null ? normalizedValue().compareTo(ismn.normalizedValue()) : -1; 041 } 042 043 @Override 044 public ISMN set(CharSequence value) { 045 this.value = value != null ? value.toString() : null; 046 return this; 047 } 048 049 @Override 050 public ISMN createChecksum(boolean createWithChecksum) { 051 this.createWithChecksum = createWithChecksum; 052 return this; 053 } 054 055 @Override 056 public ISMN normalize() { 057 Matcher m = PATTERN.matcher(value); 058 if (m.find()) { 059 this.value = value.substring(m.start(), m.end()); 060 this.value = (value.startsWith("979") ? "" : "979") + dehyphenate(value.replace('M', '0')); 061 } 062 return this; 063 } 064 065 @Override 066 public boolean isValid() { 067 return value != null && !value.isEmpty() && check(); 068 } 069 070 @Override 071 public ISMN verify() throws NumberFormatException { 072 if (value == null || value.isEmpty()) { 073 throw new NumberFormatException("invalid"); 074 } 075 if (!check()) { 076 throw new NumberFormatException("invalid createChecksum"); 077 } 078 return this; 079 } 080 081 @Override 082 public String normalizedValue() { 083 return value; 084 } 085 086 @Override 087 public String format() { 088 return value; 089 } 090 091 @Override 092 public ISMN reset() { 093 this.value = null; 094 this.createWithChecksum = false; 095 return this; 096 } 097 098 public GTIN toGTIN() throws NumberFormatException { 099 return new GTIN().set(value).normalize().verify(); 100 } 101 102 private boolean check() { 103 int l = createWithChecksum ? value.length() : value.length() - 1; 104 int checksum = 0; 105 int weight; 106 int val; 107 for (int i = 0; i < l; i++) { 108 val = value.charAt(i) - '0'; 109 weight = i % 2 == 0 ? 1 : 3; 110 checksum += val * weight; 111 } 112 int chk = 10 - checksum % 10; 113 if (createWithChecksum) { 114 char ch = (char)('0' + chk); 115 value = value + ch; 116 } 117 return chk == value.charAt(l) - '0'; 118 } 119 120 private String dehyphenate(String isbn) { 121 StringBuilder sb = new StringBuilder(isbn); 122 int i = sb.indexOf("-"); 123 while (i >= 0) { 124 sb.deleteCharAt(i); 125 i = sb.indexOf("-"); 126 } 127 return sb.toString(); 128 } 129}