001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2012
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.data;
040
041import java.io.Serializable;
042import java.net.MalformedURLException;
043import java.net.URL;
044
045import org.dcm4che3.data.Tag;
046
047/**
048 * @author Gunter Zeilinger <gunterze@gmail.com>
049 */
050public class Code implements Serializable {
051
052    private static final String NO_CODE_MEANING = "<none>";
053
054    private static final long serialVersionUID = 8807594793107889446L;
055    
056    public enum CodeValueType { SHORT, LONG, URN }; 
057
058    private String codeValue;
059    private String codingSchemeDesignator;
060    private String codingSchemeVersion;
061    private String codeMeaning;
062    private CodeValueType codeValueType;
063
064    public Code(String codeValue, String codingSchemeDesignator,
065            String codingSchemeVersion, String codeMeaning) {
066        this(codeValue, codingSchemeDesignator, codingSchemeVersion, codeMeaning,
067                        guessCodeValueType(codeValue));
068    }
069
070    public Code(String codeValue, String codingSchemeDesignator,
071            String codingSchemeVersion, String codeMeaning, CodeValueType codeValueType) {
072        if (codeValue == null)
073            throw new NullPointerException("Missing Code Value");
074        if (codeValueType == null)
075            throw new NullPointerException("Missing Code Value Type");
076        this.codeValue = codeValue;
077        init(codingSchemeDesignator, codingSchemeVersion, codeMeaning);
078        this.codeValueType = codeValueType;
079    }
080
081    public Code(String s) {
082        int len = s.length();
083        if (len < 9 
084                || s.charAt(0) != '('
085                || s.charAt(len-2) != '"'
086                || s.charAt(len-1) != ')')
087            throw new IllegalArgumentException(s);
088        
089        int endVal = s.indexOf(',');
090        int endScheme = s.indexOf(',', endVal + 1);
091        int startMeaning = s.indexOf('"', endScheme + 1) + 1;
092        this.codeValue = trimsubstring(s, 1, endVal);
093        this.codingSchemeDesignator = trimsubstring(s, endVal+1, endScheme);
094        this.codeMeaning = trimsubstring(s, startMeaning, len-2);
095        if (codingSchemeDesignator.endsWith("]")) {
096            int endVersion = s.lastIndexOf(']', endScheme - 1);
097            endScheme = s.lastIndexOf('[', endVersion - 1);
098            this.codingSchemeDesignator = trimsubstring(s, endVal+1, endScheme);
099            this.codingSchemeVersion = trimsubstring(s, endScheme+1, endVersion);
100        }
101        codeValueType = guessCodeValueType(codeValue);
102    }
103
104    private String trimsubstring(String s, int start, int end) {
105        try {
106            String trim = s.substring(start, end).trim();
107            if (!trim.isEmpty())
108                return trim;
109        } catch (StringIndexOutOfBoundsException e) {}
110        throw new IllegalArgumentException(s);
111    }
112
113    public Code(Attributes item) {
114        init(item.getString(Tag.CodingSchemeDesignator, null), item.getString(Tag.CodingSchemeVersion, null),
115             item.getString(Tag.CodeMeaning, NO_CODE_MEANING));
116        codeValue = item.getString(Tag.CodeValue, null);
117        if (codeValue == null) {
118                codeValue = item.getString(Tag.LongCodeValue, null);
119                if (codeValue == null) {
120                        codeValue = item.getString(Tag.URNCodeValue, null);
121                        if (codeValue == null) {
122                                throw new NullPointerException("Missing Code Value");
123                        }
124                        codeValueType = CodeValueType.URN;
125                } else {
126                        codeValueType = CodeValueType.LONG;
127                }
128        } else {
129                codeValueType = Code.CodeValueType.SHORT;
130        }
131    }
132
133    protected Code() {} // needed for JPA
134
135    public String getCodeValue() {
136        return codeValue;
137    }
138
139    public CodeValueType getCodeValueType() {
140        if (codeValueType == null) {
141                codeValueType = guessCodeValueType(codeValue);
142        }
143                return codeValueType;
144        }
145
146        public String getCodingSchemeDesignator() {
147        return codingSchemeDesignator;
148    }
149
150    public String getCodingSchemeVersion() {
151        return codingSchemeVersion;
152    }
153
154    public String getCodeMeaning() {
155        return codeMeaning;
156    }
157
158    @Override
159    public int hashCode() {
160        return 37 * (37 * (37 * 
161            codeValue.hashCode() +
162            codeMeaning.hashCode()) +
163            codingSchemeDesignator.hashCode()) + 
164            hashCode(codingSchemeVersion);
165    }
166
167    private int hashCode(String s) {
168        return s == null ? 0 : s.hashCode();
169    }
170
171    @Override
172    public boolean equals(Object o) {
173        return equals(o, false);
174    }
175
176    public boolean equalsIgnoreMeaning(Code o) {
177        return equals(o, true);
178    }
179
180    private boolean equals(Object o, boolean ignoreMeaning) {
181        if (o == this)
182            return true;
183        if (!(o instanceof Code))
184            return false;
185        Code other = (Code) o;
186        return codeValue.equals(other.codeValue)
187                && codingSchemeDesignator.equals(other.codingSchemeDesignator)
188                && equals(codingSchemeVersion, other.codingSchemeVersion)
189                && (ignoreMeaning || codeMeaning.equals(other.codeMeaning));
190    }
191
192    private boolean equals(String s1, String s2) {
193        return s1 == s2 || s1 != null && s1.equals(s2);
194    }
195
196    @Override
197    public String toString() {
198        StringBuilder sb = new StringBuilder();
199        sb.append('(').append(codeValue).append(", ").append(codingSchemeDesignator);
200        if (codingSchemeVersion != null)
201            sb.append(" [").append(codingSchemeVersion).append(']');
202        sb.append(", \"").append(codeMeaning).append("\")");
203        return sb.toString();
204    }
205
206    public Attributes toItem() {
207        Attributes codeItem = new Attributes(codingSchemeVersion != null ? 4 : 3);
208        switch (getCodeValueType()) {
209        case SHORT:
210                codeItem.setString(Tag.CodeValue, VR.SH, codeValue);
211                break;
212        case LONG:
213                codeItem.setString(Tag.LongCodeValue, VR.UC, codeValue);
214                break;
215        case URN:
216                codeItem.setString(Tag.URNCodeValue, VR.UR, codeValue);
217        }
218        
219        codeItem.setString(Tag.CodingSchemeDesignator, VR.SH, codingSchemeDesignator);
220        if (codingSchemeVersion != null)
221            codeItem.setString(Tag.CodingSchemeVersion, VR.SH, codingSchemeVersion);
222        codeItem.setString(Tag.CodeMeaning, VR.LO, codeMeaning);
223        return codeItem ;
224    }
225
226    private void init(String codingSchemeDesignator, String codingSchemeVersion, String codeMeaning) {
227        if (codingSchemeDesignator == null)
228            throw new NullPointerException("Missing Coding Scheme Designator");
229        if (codeMeaning == null)
230            throw new NullPointerException("Missing Code Meaning");
231        this.codingSchemeDesignator = codingSchemeDesignator;
232        this.codingSchemeVersion = codingSchemeVersion;
233        this.codeMeaning = codeMeaning;
234    }
235
236        private static CodeValueType guessCodeValueType(String codeValue) {
237                return codeValue.length() <= 16 ? CodeValueType.SHORT : 
238                        isURN(codeValue) ? CodeValueType.URN : CodeValueType.LONG;
239        }
240
241        private static boolean isURN(String codeValue) {
242        if (codeValue.indexOf(':') == -1) {
243                return false;
244        }
245        if (codeValue.startsWith("urn:")) {
246                return true;
247        }
248        try {
249                        new URL(codeValue);
250                        return true;
251                } catch (MalformedURLException e) {
252                        return false;
253                }
254    }
255}