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 * Author: Andreas Prlic
021 *
022 *
023 */
024
025package org.biojava.nbio.structure.scop;
026
027import org.biojava.nbio.structure.Structure;
028import org.biojava.nbio.structure.StructureTools;
029import org.biojava.nbio.structure.align.util.UserConfiguration;
030import org.biojava.nbio.core.util.FileDownloadUtils;
031import org.biojava.nbio.core.util.InputStreamProvider;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import java.io.*;
036import java.net.URL;
037import java.util.*;
038import java.util.concurrent.atomic.AtomicBoolean;
039
040
041/**
042 * This class provides access to the SCOP protein structure classification.
043 *
044 * For more information about SCOP see here:
045 *  <ul>
046 *   <li>SCOP: <a href="http://scop.mrc-lmb.cam.ac.uk/scop/">http://scop.mrc-lmb.cam.ac.uk/scop/</a></li>
047
048 *  <li> Introduction: <a href="http://scop.mrc-lmb.cam.ac.uk/scop/intro.html">http://scop.mrc-lmb.cam.ac.uk/scop/intro.html</a> </li>
049
050 *   <li> SCOP parsable files: <a href="http://scop.mrc-lmb.cam.ac.uk/scop/parse/">http://scop.mrc-lmb.cam.ac.uk/scop/parse/</a> </li>
051 * </ul>
052
053 *
054 * This class can automatically download missing files from the SCOP classification.
055 *
056 * @author Andreas Prlic
057 *
058 */
059public class ScopInstallation implements LocalScopDatabase {
060
061        public static final String DEFAULT_VERSION = "1.75";
062
063        private static final Logger logger = LoggerFactory.getLogger(ScopInstallation.class);
064
065        private String scopVersion;
066
067        // Stores URLs for cla, des, hie, and com files
068        private final List<ScopMirror> mirrors;
069
070        // Cache filenames (with version appended)
071        public static final String claFileName = "dir.cla.scop.txt_";
072        public static final String desFileName = "dir.des.scop.txt_";
073        public static final String hieFileName = "dir.hie.scop.txt_";
074        public static final String comFileName = "dir.com.scop.txt_";
075
076        // Download locations
077        public static final String SCOP_DOWNLOAD = "http://scop.berkeley.edu/downloads/parse/";
078        public static final String SCOP_DOWNLOAD_ALTERNATE = "http://scop.berkeley.edu/downloads/parse/";
079
080        //public static final String NEWLINE = System.getProperty("line.separator");
081        public static final String FILESPLIT = System.getProperty("file.separator");
082
083        private String cacheLocation ;
084
085        private AtomicBoolean installedCla;
086        private AtomicBoolean installedDes;
087        private AtomicBoolean installedHie;
088        private AtomicBoolean installedCom;
089
090        private Map<Integer, List<String>> commentsMap;
091        private Map<String, List<ScopDomain>> domainMap;
092        private Map<Integer, ScopDescription> sunidMap;
093        private Map<Integer, ScopNode> scopTree;
094
095
096        /**
097         * Create a new SCOP installation.
098         *
099         * @param cacheLocation where the SCOP files are stored. If they can't be found at that location they will get automatically downloaded and installed there.
100         */
101        public ScopInstallation(String cacheLocation){
102
103                setCacheLocation(cacheLocation);
104
105                installedCla = new AtomicBoolean();
106                installedCla.set(false);
107                installedDes = new AtomicBoolean();
108                installedDes.set(false);
109                installedHie = new AtomicBoolean();
110                installedHie.set(false);
111                installedCom = new AtomicBoolean();
112                installedCom.set(false);
113
114                scopVersion = DEFAULT_VERSION;
115                mirrors = new ArrayList<ScopMirror>(1);
116
117                domainMap = new HashMap<String, List<ScopDomain>>();
118
119                sunidMap  = new HashMap<Integer, ScopDescription>();
120                scopTree  = new TreeMap<Integer, ScopNode>();
121
122        }
123
124        /**
125         * Removes all of the comments (dir.com file) in order to free memory. The file will need to be reloaded if {@link #getComments(int)} is called subsequently.
126         */
127        public void nullifyComments() {
128                commentsMap = null;
129                installedCom.set(false);
130        }
131
132        /**
133         * Create a new SCOP installation, downloading the file to "the right place".
134         * This will first check for system properties or environmental variables
135         * called {@link UserConfiguration#PDB_CACHE_DIR}, or else will use a temporary directory
136         */
137        public ScopInstallation() {
138                this((new UserConfiguration()).getCacheFilePath());
139        }
140
141        public void ensureClaInstalled() throws IOException {
142                if (installedCla.get()) return;
143                if (!claFileAvailable()) downloadClaFile();
144                parseClassification();
145                installedCla.set(true);
146        }
147
148        public void ensureDesInstalled() throws IOException {
149                if (installedDes.get()) return;
150                if (!desFileAvailable()) downloadDesFile();
151                parseDescriptions();
152                installedDes.set(true);
153        }
154
155        public void ensureComInstalled() throws IOException {
156                if (installedCom.get()) return;
157                if (!comFileAvailable()) downloadComFile();
158                parseComments();
159                installedCom.set(true);
160        }
161
162        public void ensureHieInstalled() throws IOException {
163                if ( installedHie.get()) return;
164                if ( ! hieFileAvailable()) downloadHieFile();
165                parseHierarchy();
166                installedHie.set(true);
167        }
168
169        /* (non-Javadoc)
170         * @see org.biojava.nbio.structure.scop.ScopDatabase#getByCategory(org.biojava.nbio.structure.scop.ScopCategory)
171         */
172        @Override
173        public List<ScopDescription> getByCategory(ScopCategory category){
174
175                try {
176                        ensureDesInstalled();
177                } catch (IOException e) {
178                        throw new ScopIOException(e);
179                }
180
181                List<ScopDescription> matches = new ArrayList<ScopDescription>();
182                for (Integer i : sunidMap.keySet()){
183                        ScopDescription sc = sunidMap.get(i);
184                        if ( sc.getCategory().equals(category))
185
186                                try {
187                                        matches.add((ScopDescription)sc.clone());
188                                } catch (CloneNotSupportedException e) {
189                                        throw new RuntimeException("Could not clone " + ScopDescription.class + " subclass", e);
190                                }
191
192                }
193                return matches;
194        }
195
196        /* (non-Javadoc)
197         * @see org.biojava.nbio.structure.scop.ScopDatabase#filterByClassificationId(java.lang.String)
198         */
199        @Override
200        public List<ScopDescription> filterByClassificationId(String query){
201
202                try {
203                        ensureDesInstalled();
204                } catch (IOException e) {
205                        throw new ScopIOException(e);
206                }
207
208                List<ScopDescription> matches = new ArrayList<ScopDescription>();
209                for (Integer i : sunidMap.keySet()){
210                        ScopDescription sc = sunidMap.get(i);
211
212
213                        if( sc.getClassificationId().startsWith(query)){
214                                matches.add(sc);
215                        }
216                }
217
218                return matches;
219        }
220
221
222        /* (non-Javadoc)
223         * @see org.biojava.nbio.structure.scop.ScopDatabase#getTree(org.biojava.nbio.structure.scop.ScopDomain)
224         */
225        @Override
226        public List<ScopNode> getTree(ScopDomain domain){
227                ScopNode node = getScopNode(domain.getSunid());
228
229
230                List<ScopNode> tree = new ArrayList<ScopNode>();
231                while (node != null){
232
233                        //System.out.println("This node: sunid:" + node.getSunid() );
234                        //System.out.println(getScopDescriptionBySunid(node.getSunid()));
235                        node = getScopNode(node.getParentSunid());
236                        if ( node != null)
237                                tree.add(node);
238                }
239                Collections.reverse(tree);
240                return tree;
241        }
242
243        /* (non-Javadoc)
244         * @see org.biojava.nbio.structure.scop.ScopDatabase#filterByDomainName(java.lang.String)
245         */
246        @Override
247        public List<ScopDomain> filterByDomainName(String query) {
248
249                List<ScopDomain > domains = new ArrayList<ScopDomain>();
250                if (query.length() <5){
251                        return domains;
252                }
253
254                String pdbId = query.substring(1,5);
255
256                List<ScopDomain> doms = getDomainsForPDB(pdbId);
257
258
259                if ( doms == null)
260                        return domains;
261
262                query = query.toLowerCase();
263                for ( ScopDomain d: doms){
264                        if ( d.getScopId().toLowerCase().contains(query)){
265                                domains.add(d);
266                        }
267                }
268
269                return domains;
270        }
271
272        /* (non-Javadoc)
273         * @see org.biojava.nbio.structure.scop.ScopDatabase#filterByDescription(java.lang.String)
274         */
275        @Override
276        public List<ScopDescription> filterByDescription(String query) throws ScopIOException {
277                try {
278                        ensureDesInstalled();
279                } catch (IOException e) {
280                        throw new ScopIOException(e);
281                }
282
283                query = query.toLowerCase();
284                List<ScopDescription> matches = new ArrayList<ScopDescription>();
285                for (Integer i : sunidMap.keySet()){
286                        ScopDescription sc = sunidMap.get(i);
287
288                        if( sc.getDescription().toLowerCase().startsWith(query)){
289                                matches.add(sc);
290                        }
291                }
292
293                return matches;
294        }
295
296
297        /* (non-Javadoc)
298         * @see org.biojava.nbio.structure.scop.ScopDatabase#getScopDescriptionBySunid(int)
299         */
300        @Override
301        public ScopDescription getScopDescriptionBySunid(int sunid) {
302                try {
303                        ensureDesInstalled();
304                } catch (IOException e) {
305                        throw new ScopIOException(e);
306                }
307                return sunidMap.get(sunid);
308        }
309
310        /* (non-Javadoc)
311         * @see org.biojava.nbio.structure.scop.ScopDatabase#getDomainsForPDB(java.lang.String)
312         */
313        @Override
314        public  List<ScopDomain> getDomainsForPDB(String pdbId) {
315
316                try {
317                        ensureClaInstalled();
318                } catch (IOException e) {
319                        throw new ScopIOException(e);
320                }
321
322                List<ScopDomain> doms = domainMap.get(pdbId.toLowerCase());
323
324                List<ScopDomain> retdoms = new ArrayList<ScopDomain>();
325
326                if ( doms == null)
327                        return retdoms;
328
329                for ( ScopDomain d : doms){
330                        try {
331                                ScopDomain n = (ScopDomain) d.clone();
332                                retdoms.add(n);
333                        }  catch (CloneNotSupportedException e){
334                                throw new RuntimeException(ScopDomain.class + " subclass does not support clone()", e);
335                        }
336
337
338                }
339                return retdoms;
340        }
341
342        /* (non-Javadoc)
343         * @see org.biojava.nbio.structure.scop.ScopDatabase#getDomainByScopID(java.lang.String)
344         */
345        @Override
346        public ScopDomain getDomainByScopID(String scopId) {
347
348                try {
349                        ensureClaInstalled();
350                } catch (IOException e) {
351                        throw new ScopIOException(e);
352                }
353
354                if ( scopId.length() < 6) {
355                        throw new ScopIOException("Does not look like a scop ID! " + scopId);
356                }
357                String pdbId = scopId.substring(1,5);
358                List<ScopDomain> doms = getDomainsForPDB(pdbId);
359                if ( doms == null)
360                        return null;
361                for ( ScopDomain d : doms){
362                        if ( d.getScopId().equalsIgnoreCase(scopId))
363                                return d;
364                }
365
366                return null;
367        }
368
369        /* (non-Javadoc)
370         * @see org.biojava.nbio.structure.scop.ScopDatabase#getScopNode(int)
371         */
372        @Override
373        public ScopNode getScopNode(int sunid){
374
375                try {
376                        ensureHieInstalled();
377                } catch (IOException e) {
378                        throw new ScopIOException(e);
379                }
380
381                return scopTree.get(sunid);
382        }
383
384
385        private void parseClassification() throws IOException{
386
387                File file = new File(getClaFilename());
388
389                InputStreamProvider ips = new InputStreamProvider();
390                BufferedReader buffer = new BufferedReader (new InputStreamReader(ips.getInputStream(file)));
391
392                parseClassification(buffer);
393
394        }
395
396        private void parseHierarchy() throws IOException{
397
398                File file = new File(getHieFilename());
399
400
401                InputStreamProvider ips = new InputStreamProvider();
402                BufferedReader buffer = new BufferedReader (new InputStreamReader(ips.getInputStream(file)));
403
404                parseHierarchy(buffer);
405
406        }
407
408        private void parseHierarchy(BufferedReader buffer) throws IOException {
409                String line;
410
411                int counter =0;
412                while ((line = buffer.readLine ()) != null) {
413                        if ( line.startsWith("#"))
414                                continue;
415
416                        String[] spl  = line.split("\t");
417
418                        if ( spl.length != 3 ) {
419                                throw new IOException("parseHierarchy: Can't parse line " + line +" (length: " + spl.length+")");
420                        }
421                        counter++;
422                        int sunid       = Integer.parseInt(spl[0]);
423                        int parentSunid = -1;
424
425                        if ( sunid != 0)
426                                parentSunid = Integer.parseInt(spl[1]);
427
428                        String children = spl[2];
429                        String[] childIds = children.split(",");
430
431                        List<Integer> chis = new ArrayList<Integer>();
432
433                        for ( String id : childIds){
434                                if ( id.equals("-"))
435                                        continue;
436                                chis.add(Integer.parseInt(id));
437                        }
438
439                        ScopNode node = new ScopNode();
440
441                        node.setSunid(sunid);
442                        node.setParentSunid(parentSunid);
443                        node.setChildren(chis);
444
445                        scopTree.put(sunid, node);
446                }
447                logger.info("Parsed {} SCOP sunid nodes.", counter);
448        }
449
450
451        private void parseDescriptions() throws IOException{
452
453                File file = new File(getDesFilename());
454
455                InputStreamProvider ips = new InputStreamProvider();
456                BufferedReader buffer = new BufferedReader (new InputStreamReader(ips.getInputStream(file)));
457
458                parseDescriptions(buffer);
459
460        }
461
462        private void parseComments() throws IOException{
463
464                File file = new File(getComFilename());
465
466                InputStreamProvider ips = new InputStreamProvider();
467                BufferedReader buffer = new BufferedReader (new InputStreamReader(ips.getInputStream(file)));
468
469                parseComments(buffer);
470
471        }
472
473        private void parseComments(BufferedReader buffer) throws IOException {
474
475                commentsMap = new HashMap<Integer,List<String>>();
476
477                int counter = 0;
478                String line;
479                while ((line = buffer.readLine ()) != null) {
480                        if (line.startsWith("#")) continue;
481                        String[] parts = line.split("!");
482                        int sunId = Integer.parseInt(parts[0].trim());
483                        if (parts.length == 1) {
484                                commentsMap.put(sunId, new ArrayList<String>(1));
485                                continue;
486                        }
487                        List<String> comments = new ArrayList<String>(parts.length - 1);
488                        for (int i = 1; i < parts.length; i++) {
489                                String trimmed = parts[i].trim();
490                                if( !trimmed.isEmpty() ) {
491                                        comments.add(trimmed);
492                                }
493                        }
494                        commentsMap.put(sunId, comments);
495                        counter++;
496                }
497                logger.info("Parsed {} SCOP comments.", counter);
498
499        }
500
501        private void parseDescriptions(BufferedReader buffer) throws IOException {
502                String line = null;
503
504                int counter = 0;
505                while ((line = buffer.readLine ()) != null) {
506                        if ( line.startsWith("#"))
507                                continue;
508
509                        String[] spl  = line.split("\t");
510
511                        if ( spl.length != 5 ) {
512                                throw new IOException("parseDescriptions: Can't parse line " + line +" (length: " + spl.length+")");
513                        }
514                        counter++;
515
516                        //46464  dm  a.1.1.2 -   Hemoglobin I
517                        int sunID = Integer.parseInt(spl[0]);
518                        ScopCategory category =  ScopCategory.fromString(spl[1]);
519                        String classificationId = spl[2];
520                        String name = spl[3];
521                        String desc = spl[4];
522
523                        ScopDescription c = new ScopDescription();
524                        c.setSunID(sunID);
525                        c.setCategory(category);
526                        c.setClassificationId(classificationId);
527                        c.setName(name);
528                        c.setDescription(desc);
529
530                        sunidMap.put(sunID, c);
531
532                }
533                logger.info("Parsed {} SCOP sunid descriptions.", counter);
534        }
535
536
537
538        private void parseClassification(BufferedReader buffer) throws IOException {
539                String line = null;
540
541                int counter = 0;
542                while ((line = buffer.readLine ()) != null) {
543                        if ( line.startsWith("#"))
544                                continue;
545
546                        String[] spl  = line.split("\t");
547
548                        if ( spl.length != 6){
549                                throw new IOException("Can't parse line " + line);
550                        }
551                        counter++;
552
553                        String scopId = spl[0];
554                        String pdbId = spl[1];
555                        String range = spl[2];
556                        String classificationId = spl[3];
557                        Integer sunid = Integer.parseInt(spl[4]);
558                        String tree = spl[5];
559
560
561
562                        ScopDomain d = new ScopDomain();
563                        d.setScopId(scopId);
564                        d.setPdbId(pdbId);
565
566                        d.setRanges(extractRanges(range));
567
568                        d.setClassificationId(classificationId);
569                        d.setSunid(sunid);
570
571                        String[] treeSplit = tree.split(",");
572
573                        if (  treeSplit.length != 7 ) {
574                                throw new IOException("Can't process: " + line );
575                        }
576
577                        int classId =Integer.parseInt(treeSplit[0].substring(3));
578                        int foldId = Integer.parseInt(treeSplit[1].substring(3));
579                        int superfamilyId = Integer.parseInt(treeSplit[2].substring(3));
580                        int familyId = Integer.parseInt(treeSplit[3].substring(3));
581                        int domainId = Integer.parseInt(treeSplit[4].substring(3));
582                        int speciesId = Integer.parseInt(treeSplit[5].substring(3));
583                        int px = Integer.parseInt(treeSplit[6].substring(3));
584
585                        d.setClassId(classId);
586                        d.setFoldId(foldId);
587                        d.setSuperfamilyId(superfamilyId);
588                        d.setFamilyId(familyId);
589                        d.setDomainId(domainId);
590                        d.setSpeciesId(speciesId);
591                        d.setPx(px);
592
593                        List<ScopDomain> domainList;
594                        if ( domainMap.containsKey(pdbId)){
595                                domainList = domainMap.get(pdbId);
596                        } else {
597                                domainList = new ArrayList<ScopDomain>();
598                                domainMap.put(pdbId,domainList);
599                        }
600
601                        domainList.add(d);
602                }
603                logger.info("Parsed {} SCOP sunid domains.", counter);
604
605        }
606
607        /**
608         * Converts the SCOP range field into a list of subranges suitable for
609         * storage in a ScopDomain object. Each range should be of a format
610         * compatible with {@link StructureTools#getSubRanges(Structure,String)}.
611         * @param range
612         * @return
613         */
614        private List<String> extractRanges(String range) {
615                List<String> ranges;
616                String[] rangeSpl = range.split(",");
617
618                // Recent versions of scop always specify a chain, so no processing is needed
619                if(scopVersion.compareTo("1.73") < 0 ) {
620                        for(int i=0; i<rangeSpl.length;i++) {
621                                String subRange = rangeSpl[i];
622
623                                // Allow single-chains, as well as the '-' special case
624                                if(subRange.length()<2) {
625                                        continue;
626                                }
627
628                                // Allow explicit chain syntax
629                                if(subRange.charAt(1) != ':') {
630                                        // Early versions sometimes skip the chain identifier for single-chain domains
631                                        // Indicate this with a chain "_"
632                                        rangeSpl[i] = "_:"+subRange;
633                                }
634                        }
635                }
636                ranges = Arrays.asList(rangeSpl);
637                return ranges;
638        }
639
640        protected void downloadClaFile() throws FileNotFoundException, IOException{
641                if(mirrors.size()<1) {
642                        initScopURLs();
643                }
644                IOException exception = null;
645                for(ScopMirror mirror:mirrors) {
646                        try {
647                                URL url = new URL(mirror.getClaURL(scopVersion));
648
649                                String localFileName = getClaFilename();
650                                File localFile = new File(localFileName);
651
652                                downloadFileFromRemote(url, localFile);
653                                return;
654                        } catch(IOException e ) {
655                                exception = e;
656                        }
657                }
658                throw new IOException("Unable to download SCOP .cla file",exception);
659        }
660
661        protected void downloadDesFile() throws FileNotFoundException, IOException{
662                if(mirrors.size()<1) {
663                        initScopURLs();
664                }
665                IOException exception = null;
666                for(ScopMirror mirror:mirrors) {
667                        try {
668                                URL url = new URL(mirror.getDesURL( scopVersion));
669
670                                String localFileName = getDesFilename();
671                                File localFile = new File(localFileName);
672
673                                downloadFileFromRemote(url, localFile);
674                                return;
675                        } catch(IOException e ) {
676                                exception = e;
677                        }
678                }
679                throw new IOException("Unable to download SCOP .des file",exception);
680        }
681
682        protected void downloadHieFile() throws IOException{
683                if(mirrors.size()<1) {
684                        initScopURLs();
685                }
686                IOException exception = null;
687                for(ScopMirror mirror:mirrors) {
688                        try {
689                                URL url = new URL(mirror.getHieURL( scopVersion));
690
691                                String localFileName = getHieFilename();
692                                File localFile = new File(localFileName);
693
694                                downloadFileFromRemote(url, localFile);
695                                return;
696                        } catch(IOException e ) {
697                                exception = e;
698                        }
699                }
700                throw new IOException("Unable to download SCOP .hie file",exception);
701
702        }
703
704        protected void downloadComFile() throws FileNotFoundException, IOException{
705                if(mirrors.size()<1) {
706                        initScopURLs();
707                }
708                IOException exception = null;
709                for(ScopMirror mirror:mirrors) {
710                        try {
711                                URL url = new URL(mirror.getComURL(scopVersion));
712
713                                String localFileName = getComFilename();
714                                File localFile = new File(localFileName);
715
716                                downloadFileFromRemote(url, localFile);
717                                return;
718                        } catch (IOException e ) {
719                                exception = e;
720                        }
721                }
722                throw new IOException("Unable to download SCOP .com file",exception);
723        }
724
725        protected void downloadFileFromRemote(URL remoteURL, File localFile) throws IOException{
726                logger.info("Downloading " + remoteURL + " to: " + localFile);
727                FileDownloadUtils.downloadFile(remoteURL, localFile);
728        }
729
730        private boolean claFileAvailable(){
731                String fileName = getClaFilename();
732
733                File f = new File(fileName);
734
735                return f.exists() && f.length()>0;
736        }
737
738        private boolean desFileAvailable(){
739                String fileName = getDesFilename();
740
741                File f = new File(fileName);
742                return f.exists() && f.length()>0;
743        }
744
745        private boolean hieFileAvailable(){
746                String fileName = getHieFilename();
747
748                File f = new File(fileName);
749
750                return f.exists() && f.length()>0;
751        }
752
753        private boolean comFileAvailable(){
754                String fileName = getComFilename();
755
756                File f = new File(fileName);
757
758                return f.exists() && f.length()>0;
759        }
760
761        protected String getClaFilename(){
762                return cacheLocation + claFileName + scopVersion;
763        }
764
765        protected String getDesFilename(){
766                return cacheLocation + desFileName + scopVersion;
767
768        }
769
770        protected String getHieFilename(){
771                return cacheLocation + hieFileName + scopVersion;
772
773        }
774
775        protected String getComFilename(){
776                return cacheLocation + comFileName + scopVersion;
777        }
778
779        public String getCacheLocation() {
780                return cacheLocation;
781        }
782
783        public void setCacheLocation(String cacheLocation) {
784
785                if (! cacheLocation.endsWith(FILESPLIT))
786                        cacheLocation += FILESPLIT;
787                this.cacheLocation = cacheLocation;
788
789
790        }
791        /* (non-Javadoc)
792         * @see org.biojava.nbio.structure.scop.ScopDatabase#getScopVersion()
793         */
794        @Override
795        public String getScopVersion() {
796                return scopVersion;
797        }
798        @Override
799        public void setScopVersion(String scopVersion) {
800                if(scopVersion == null)
801                        throw new NullPointerException("Null scop version");
802                if(this.scopVersion.equals(scopVersion))
803                        return;
804                this.scopVersion = scopVersion;
805                // reset installation flags
806                installedCla.set(false);
807                installedDes.set(false);
808                installedHie.set(false);
809                installedCom.set(false);
810
811        }
812
813        public void addMirror(String scopDownloadURL) {
814                mirrors.add(new ScopMirror(scopDownloadURL));
815        }
816        void addMirror(ScopMirror scopURLs) {
817                mirrors.add(scopURLs);
818        }
819        public List<ScopMirror> getMirrors() {
820                if(mirrors.isEmpty()) {
821                        this.initScopURLs();
822                }
823                return mirrors;
824        }
825
826        /* (non-Javadoc)
827         * @see org.biojava.nbio.structure.scop.ScopDatabase#getScopDomainsBySunid(java.lang.Integer)
828         */
829        @Override
830        public List<ScopDomain> getScopDomainsBySunid(Integer sunid)
831        {
832
833                try {
834                        ensureClaInstalled();
835                } catch (IOException e) {
836                        throw new ScopIOException(e);
837                }
838
839                List<ScopDomain> domains = new ArrayList<ScopDomain>();
840
841                for (String pdbId: domainMap.keySet()){
842                        for (ScopDomain d : domainMap.get(pdbId)){
843                                try {
844                                        if ( d.getPx() == sunid) {
845                                                domains.add((ScopDomain)d.clone());
846                                        } else if ( d.getSpeciesId() == sunid ){
847                                                domains.add((ScopDomain)d.clone());
848                                        }else if ( d.getDomainId() == sunid ){
849                                                domains.add((ScopDomain)d.clone());
850                                        }else if ( d.getFamilyId() == sunid ){
851                                                domains.add((ScopDomain)d.clone());
852                                        }else if ( d.getSuperfamilyId() == sunid ){
853                                                domains.add((ScopDomain)d.clone());
854                                        }else if ( d.getFoldId() == sunid ){
855                                                domains.add((ScopDomain)d.clone());
856                                        }else if ( d.getClassId() == sunid ){
857                                                domains.add((ScopDomain)d.clone());
858                                        } else {
859                                                throw new RuntimeException("Type " + d + " not recognized"); // only possible if SCOP changes
860                                        }
861                                } catch (CloneNotSupportedException e){
862                                        throw new RuntimeException(ScopDomain.class + " subclass does not support clone()", e);
863                                }
864                        }
865                }
866                return domains;
867
868        }
869
870        @Override
871        public List<String> getComments(int sunid) {
872                try {
873                        ensureComInstalled();
874                } catch (IOException e) {
875                        throw new ScopIOException(e);
876                }
877                if (!commentsMap.containsKey(sunid)) return new ArrayList<String>(1);
878                return commentsMap.get(sunid);
879        }
880
881
882        private void initScopURLs() {
883                if(!this.mirrors.isEmpty()) {
884                        return;
885                }
886
887                // first, try default scop
888                ScopMirror primary = new ScopMirror();
889                // If unreachable, try alternate Berkeley location
890                ScopMirror alt = new ScopMirror(
891                                SCOP_DOWNLOAD_ALTERNATE,
892                                "dir.cla.scop.%s.txt","dir.des.scop.%s.txt",
893                                "dir.hie.scop.%s.txt","dir.com.scop.%s.txt");
894                mirrors.add(primary);
895                mirrors.add(alt);
896        }
897}