001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.utils.lsid;
023
024import java.io.Serializable;
025import java.util.StringTokenizer;
026
027/**
028 * Life Science Identifier (LSID).
029 *
030 * LSID syntax:
031 * <p>
032 * urn:lsid:&lt;authorityId&gt;:&lt;namespaceId&gt;:&lt;objectId&gt;:&lt;revisionId&gt;
033 * <p>
034 * The elements of a LSID are as follows:
035 * <ul>
036 * <li>authorityId = &lt;authorityId&gt; identifies the organization
037 * <li>namespaceId = &lt;namespaceId&gt; namespace to scope the identifier value
038 * <li>objectId = &lt;objectId&gt; identifier of the object within namespace
039 * <li>revisionId = &lt;revisionId&gt; optional version information
040 * </ul>
041 *
042 * <p>Examples:
043 * <pre>
044 * urn:lsid:ebi.ac.uk:SWISS-PROT/accession:P34355:3
045 * urn:lsid:rcsb.org:PDB:1D4X:22
046 * urn:lsid:ncbi.nlm.nih.gov:Genbank/accession:NT_001063:2
047 * </pre></p>
048 *
049 * <p>As described in the memo <i>URN Namespace for Life Science Identifiers</i><br/>
050 * &gt; <a href="http://www.i3c.org/workgroups/technical_architecture/resources/lsid/docs/LSIDSyntax9-20-02.htm">
051 * http://www.i3c.org/workgroups/technical_architecture/resources/lsid/docs/LSIDSyntax9-20-02.htm</a></p>
052 *
053 * <p>TODO:
054 * <br/>
055 * Should this class support the spec definition of &quot;lexical
056 * equivalence&quot; in the equals(Object) method, or by the introduction
057 * of a new method <code>boolean lexicallyEquivalent(LifeScienceIdentifier)</code>?
058 * </p>
059 * <p>From the spec:
060 * <br/>
061 * For various purposes such as caching and replication, it's often desirable
062 * to determine if two LSIDs are the same without resolving them. The general
063 * purpose means of doing so is by testing for "lexical equivalence" as defined:
064 * LSIDs are lexically equivalent if the AuthorityID, NamespaceID, ObjectID, and
065 * RevisionID are all identical (case-insensitive comparison).
066 * </p>
067 *
068 * @author Michael Heuer
069 */
070public final class LifeScienceIdentifier
071    implements Serializable
072{
073    private final String authorityId;
074    private final String namespaceId;
075    private final String objectId;
076    private final String revisionId;
077
078    /**
079     * Create a new LifeScienceIdentifier.
080     *
081     * @param authorityId identifies the organization
082     * @param namespaceId namespace to scope the identifier value
083     * @param objectId identifer of the object within namespace
084     * @param revisionId version information
085     *
086     * @throws IllegalArgumentException if any of <code>authorityId</code>,
087     *   <code>namespaceId</code>, or <code>objectId</code> are null
088     */
089    private LifeScienceIdentifier(String authorityId,
090                                  String namespaceId,
091                                  String objectId,
092                                  String revisionId)
093    {
094        if (authorityId == null)
095            throw new IllegalArgumentException("authority must not be null");
096        if (namespaceId == null)
097            throw new IllegalArgumentException("namespace must not be null");
098        if (objectId == null)
099            throw new IllegalArgumentException("objectId must not be null");
100        
101        this.authorityId = authorityId;
102        this.namespaceId = namespaceId;
103        this.objectId = objectId;
104        this.revisionId = revisionId;
105    }
106
107    /**
108     * Return the authority id for this identifier.
109     */
110    public String getAuthorityId()
111    {
112        return authorityId;
113    }
114    
115    /**
116     * Return the namespace id for this identifier
117     * within the authority.
118     */
119    public String getNamespaceId()
120    {
121        return namespaceId;
122    }
123    
124    /**
125     * Return the object id of this identifier.
126     */
127    public String getObjectId()
128    {
129        return objectId;
130    }
131    
132    /**
133     * Return the revision id of this identifier.
134     * May return null.
135     */
136    public String getRevisionId()
137    {
138        return revisionId;
139    }
140    
141    public boolean equals(Object value)
142    {
143        if (this == value)
144            return true;
145        if (!(value instanceof LifeScienceIdentifier))
146            return false;
147        
148        LifeScienceIdentifier lsid = (LifeScienceIdentifier) value;
149        
150        return (authorityId.equals(lsid.getAuthorityId()) &&
151                namespaceId.equals(lsid.getNamespaceId()) &&
152                objectId.equals(lsid.getObjectId()) &&
153                (revisionId == null ? lsid.getRevisionId() == null : revisionId.equals(lsid.getRevisionId())));
154    }
155    
156    public int hashCode()
157    {
158      return toString().hashCode();
159    }
160    
161    public String toString()
162    {
163        StringBuffer sb = new StringBuffer();
164        sb.append("urn:lsid:");
165        sb.append(getAuthorityId());
166        sb.append(":");
167        sb.append(getNamespaceId());
168        sb.append(":");
169        sb.append(getObjectId());
170        
171        if (getRevisionId() != null)
172        {
173            sb.append(":");
174            sb.append(getRevisionId());
175        }
176        
177        return (sb.toString());
178    }
179    
180    /**
181     * Create a new LifeScienceIdentifier parsed
182     * from the properly formatted string <code>lsid</code>.
183     *
184     * @param lsid formatted LifeScienceIdentifier string
185     *
186     * @throws LifeScienceIdentifierParseException if <code>lsid</code>
187     *    is not properly formatted
188     */
189    public static LifeScienceIdentifier valueOf(String lsid)
190        throws LifeScienceIdentifierParseException
191    {
192        if (lsid.length() < 9)
193            throw new LifeScienceIdentifierParseException("couldn't parse: "
194                                                          + lsid
195                                                          + ", didn't contain urn prefix");
196        
197        String urnPrefix = lsid.substring(0,9);
198        lsid = lsid.substring(9);
199        
200        if (!("urn:lsid:".equalsIgnoreCase(urnPrefix)))
201            throw new LifeScienceIdentifierParseException("couldn't parse: "
202                                                          + lsid
203                                                          + ", incorrect urn prefix");
204        
205        StringTokenizer st = new StringTokenizer(lsid, ":", true);
206        
207        int count = st.countTokens();
208        if (count >= 5)
209        {
210            String authorityId = st.nextToken();
211            st.nextToken();
212            String namespaceId = st.nextToken();
213            st.nextToken();
214            String objectId = st.nextToken(); 
215            
216            String revisionId = null;
217            if (count >= 6)
218            {
219                st.nextToken();
220                revisionId = "";
221            }
222            if (st.hasMoreTokens())
223            {
224                revisionId = st.nextToken();
225            }
226            if (st.hasMoreTokens())
227                throw new LifeScienceIdentifierParseException("couldn't parse: "
228                                                              + lsid
229                                                              + ", too many tokens");
230            
231            return valueOf(authorityId, namespaceId, objectId, revisionId);
232        }
233        else
234        {
235            throw new LifeScienceIdentifierParseException("couldn't parse: "
236                                                          + lsid
237                                                          + ", badly formatted lsid");
238        }
239    }
240    
241    /**
242     * Create a new LifeScienceIdentifier from the
243     * specified parameters.
244     *
245     * @param authorityId identifies the organization
246     * @param namespaceId namespace to scope the identifier value
247     * @param objectId identifer of the object within namespace
248     * @param revisionId optional version information
249     *
250     * @throws NullPointerException if any of <code>authorityId</code>,
251     *   <code>namespaceId</code>, or <code>objectId</code> are null
252     */ 
253    public static LifeScienceIdentifier valueOf(String authorityId,
254                                                String namespaceId,
255                                                String objectId,
256                                                String revisionId)
257    {
258        if ((authorityId == null) ||
259            (namespaceId == null) ||
260            (objectId == null))
261            throw new NullPointerException("all of authorityId, namespaceId, objectId " +
262                                           "must not be null");
263
264        return new LifeScienceIdentifier(authorityId,
265                                         namespaceId,
266                                         objectId,
267                                         revisionId);
268    }
269
270    /**
271     * Create a new LifeScienceIdentifier from the
272     * specified parameters.
273     *
274     * @param authorityId identifies the organization
275     * @param namespaceId namespace to scope the identifier value
276     * @param objectId identifer of the object within namespace
277     *
278     * @throws NullPointerException if any of <code>authorityId</code>,
279     *   <code>namespaceId</code>, or <code>objectId</code> are null
280     */ 
281    public static LifeScienceIdentifier valueOf(String authorityId,
282                                                String namespaceId,
283                                                String objectId)
284    {
285        if ((authorityId == null) ||
286            (namespaceId == null) ||
287            (objectId == null))
288            throw new NullPointerException("all of authorityId, namespaceId, objectId " +
289                                           "must not be null");
290
291        return new LifeScienceIdentifier(authorityId,
292                                         namespaceId,
293                                         objectId,
294                                         null);
295    }
296    
297    private static final long serialVersionUID = 8478038493421763123L;
298}