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 */
021package org.biojava.nbio.structure;
022
023import java.util.ArrayList;
024import java.util.Iterator;
025import java.util.List;
026
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030/**
031 * A chain, a start residue, and an end residue.
032 *
033 * Also stores a length. Because of insertion codes, this length is not necessarily {@code end − start}.
034 */
035public class ResidueRangeAndLength extends ResidueRange {
036        private static final Logger logger = LoggerFactory.getLogger(ResidueRangeAndLength.class);
037
038        private final int length;
039
040        public ResidueRangeAndLength(String chain, ResidueNumber start, ResidueNumber end, int length) {
041                super(chain, start, end);
042                this.length = length;
043        }
044
045        public ResidueRangeAndLength(String chain, String start, String end, int length) {
046                super(chain, start, end);
047                this.length = length;
048        }
049
050        /**
051         * Returns a new Iterator over every {@link ResidueNumber} in this ResidueRange.
052         * Stores the contents of {@code map} until the iterator is finished, so calling code should set the iterator to {@code null} if it did not finish.
053         */
054        @Override
055        public Iterator<ResidueNumber> iterator(AtomPositionMap map) {
056                return super.iterator(map); // just a bit faster
057        }
058
059        /**
060         * Calculates the combined number of residues of the ResidueRanges in {@code rrs}.
061         *
062         * Assumes no overlap. If two or more ranges cover the same residues, will over-count the union of the residues.
063         *
064         * @param rrs
065         *            A list of ResidueRanges
066         * @return The combined length
067         * @throws IllegalArgumentException
068         *             If the {@link #getLength() length} of one or more ResidueRange is null
069         * @see #getLength()
070         */
071        public static int calcLength(List<ResidueRangeAndLength> rrs) {
072                int l = 0;
073                for (ResidueRangeAndLength rr : rrs) {
074                        l += rr.getLength();
075                }
076                return l;
077        }
078
079        /**
080         * Parses a residue range.
081         *
082         * The AtomPositionMap is used to calculate the length and fill in missing
083         * information, such as for whole chains ('A:'). Supports the special chain
084         * name '_' for single-chain structures.
085         *
086         * If residues are specified outside of the range given in the map,
087         * attempts to decrease the input range to valid values. In extreme cases
088         * where this process fails fails to find any valid indices, returns null.
089         *
090         * For a function which more conservatively represents the input range,
091         * without chain inference and error fixes, use {@link ResidueRange#parse(String)}.
092         * @param s
093         *            A string of the form chain_start-end. For example: <code>A.5-100</code>.
094         * @return The unique ResidueRange corresponding to {@code s}.
095         */
096        public static ResidueRangeAndLength parse(String s, AtomPositionMap map) {
097                ResidueRange rr = parse(s);
098                ResidueNumber start = rr.getStart();
099
100                String chain = rr.getChainId();
101
102                // handle special "_" chain
103                if(chain == null || chain.equals("_")) {
104                        ResidueNumber first = map.getNavMap().firstKey();
105                        chain = first.getChainId();
106                        // Quick check for additional chains. Not guaranteed if the atoms are out of order.
107                        if( ! map.getNavMap().lastKey().getChainId().equals(chain) ) {
108                                logger.warn("Multiple possible chains match '_'. Using chain {}",chain);
109                        }
110                }
111
112                // get a non-null start and end
113                // if it's the whole chain, choose the first and last residue numbers in the chain
114                if (start==null) {
115                        start = map.getFirst(chain);
116                }
117                ResidueNumber end = rr.getEnd();
118                if (end==null) { // should happen iff start==null
119                        end = map.getLast(chain);
120                }
121
122                // Replace '_'
123                start.setChainId(chain);
124                end.setChainId(chain);
125
126                // Now fix any errors and calculate the length
127                return map.trimToValidResidues(new ResidueRange(chain, start, end));
128        }
129
130
131        public static List<ResidueRangeAndLength> parseMultiple(List<String> ranges, AtomPositionMap map) {
132                List<ResidueRangeAndLength> rrs = new ArrayList<ResidueRangeAndLength>(ranges.size());
133                for (String range : ranges) {
134                        ResidueRangeAndLength rr = ResidueRangeAndLength.parse(range, map);
135                        if (rr != null) rrs.add(rr);
136                }
137                return rrs;
138        }
139
140        /**
141         * @param s
142         *            A string of the form chain_start-end,chain_start-end, ... For example:
143         *            <code>A.5-100,R_110-190,Z_200-250</code>.
144         * @return The unique ResidueRange corresponding to {@code s}.
145         */
146        public static List<ResidueRangeAndLength> parseMultiple(String s, AtomPositionMap map) {
147                String[] parts = s.split(",");
148                List<ResidueRangeAndLength> list = new ArrayList<ResidueRangeAndLength>(parts.length);
149                for (String part : parts) {
150                        list.add(parse(part, map));
151                }
152                return list;
153        }
154
155        /**
156         * @return The number of residues in this ResidueRange
157         */
158        public int getLength() {
159                return length;
160        }
161
162        @Override
163        public boolean equals(Object o) {
164                if (this == o) {
165                        return true;
166                }
167                if (o == null || getClass() != o.getClass()) {
168                        return false;
169                }
170                if (!super.equals(o)) {
171                        return false;
172                }
173                ResidueRangeAndLength that = (ResidueRangeAndLength) o;
174                return length == that.length;
175        }
176
177        @Override
178        public int hashCode() {
179                int result = super.hashCode();
180                result = 31 * result + length;
181                return result;
182        }
183}