JAL-2416 allow space in score matrix name
[jalview.git] / src / jalview / io / ScoreMatrixFile.java
1 package jalview.io;
2
3 import jalview.analysis.scoremodels.ScoreMatrix;
4 import jalview.analysis.scoremodels.ScoreModels;
5 import jalview.datamodel.SequenceI;
6
7 import java.io.IOException;
8 import java.util.StringTokenizer;
9
10 /**
11  * A class that can parse a file containing a substitution matrix and register
12  * it for use in Jalview
13  * 
14  * @author gmcarstairs
15  *
16  */
17 // TODO modify the AlignFile / IdentifyFile pattern so that non-alignment files
18 // like this are handled more naturally
19 public class ScoreMatrixFile extends AlignFile implements
20         AlignmentFileReaderI
21 {
22   // first non-comment line identifier - also checked in IdentifyFile
23   public static final String SCOREMATRIX = "SCOREMATRIX";
24
25   private static final String DELIMITERS = " ,\t";
26
27   private static final String COMMENT_CHAR = "#";
28
29   private String matrixName;
30
31   /**
32    * Constructor
33    * 
34    * @param source
35    * @throws IOException
36    */
37   public ScoreMatrixFile(FileParse source) throws IOException
38   {
39     super(false, source);
40   }
41
42   @Override
43   public String print(SequenceI[] sqs, boolean jvsuffix)
44   {
45     return null;
46   }
47
48   /**
49    * Parses the score matrix file, and if successful registers the matrix so it
50    * will be shown in Jalview menus.
51    */
52   @Override
53   public void parse() throws IOException
54   {
55     ScoreMatrix sm = parseMatrix();
56
57     ScoreModels.getInstance().registerScoreModel(sm);
58   }
59
60   /**
61    * Parses the score matrix file and constructs a ScoreMatrix object. If an
62    * error is found in parsing, it is thrown as FileFormatException. Any
63    * warnings are written to syserr.
64    * 
65    * @return
66    * @throws IOException
67    */
68   public ScoreMatrix parseMatrix() throws IOException
69   {
70     ScoreMatrix sm = null;
71     int lineNo = 0;
72     String name = null;
73     String alphabet = null;
74     float[][] scores = null;
75     int size = 0;
76     int row = 0;
77     String err = null;
78     String data;
79
80     while ((data = nextLine()) != null)
81     {
82       lineNo++;
83       data = data.trim();
84       if (data.startsWith(COMMENT_CHAR) || data.length() == 0)
85       {
86         continue;
87       }
88       if (data.toUpperCase().startsWith(SCOREMATRIX))
89       {
90         /*
91          * Parse name from ScoreMatrix <name>
92          * we allow any delimiter after ScoreMatrix then take the rest of the line
93          */
94         if (name != null)
95         {
96           System.err
97                   .println("Warning: 'ScoreMatrix' repeated in file at line "
98                           + lineNo);
99         }
100         StringTokenizer nameLine = new StringTokenizer(data, DELIMITERS);
101         if (nameLine.countTokens() < 2)
102         {
103           err = "Format error: expected 'ScoreMatrix <name>', found '"
104                   + data + "' at line " + lineNo;
105           throw new FileFormatException(err);
106         }
107         nameLine.nextToken(); // 'ScoreMatrix'
108         name = nameLine.nextToken(); // next field
109         name = data.substring(1).substring(data.substring(1).indexOf(name));
110         continue;
111       }
112       else if (name == null)
113       {
114         err = "Format error: 'ScoreMatrix <name>' should be the first non-comment line";
115         throw new FileFormatException(err);
116       }
117
118       /*
119        * next line after ScoreMatrix should be the alphabet of scored symbols
120        */
121       if (alphabet == null)
122       {
123         alphabet = data;
124         size = alphabet.length();
125         scores = new float[size][];
126         continue;
127       }
128
129       /*
130        * too much information
131        */
132       if (row >= size)
133       {
134         err = "Unexpected extra input line in score model file: '" + data
135                 + "'";
136         throw new FileFormatException(err);
137       }
138
139       /*
140        * subsequent lines should be the symbol scores
141        * optionally with the symbol as the first column for readability
142        */
143       StringTokenizer scoreLine = new StringTokenizer(data, DELIMITERS);
144       if (scoreLine.countTokens() == size + 1)
145       {
146         /*
147          * check 'guide' symbol is the row'th letter of the alphabet
148          */
149         String symbol = scoreLine.nextToken();
150         if (symbol.length() > 1 || symbol.charAt(0) != alphabet.charAt(row))
151         {
152           err = String
153                   .format("Error parsing score matrix at line %d, expected '%s' but found '%s'",
154                           lineNo, alphabet.charAt(row), symbol);
155           throw new FileFormatException(err);
156         }
157       }
158       if (scoreLine.countTokens() != size)
159       {
160         err = String.format("Expected %d scores at line %d but found %d",
161                 size, lineNo, scoreLine.countTokens());
162         throw new FileFormatException(err);
163       }
164       scores[row] = new float[size];
165       int col = 0;
166       String value = null;
167       while (scoreLine.hasMoreTokens())
168       {
169         try
170         {
171           value = scoreLine.nextToken();
172           scores[row][col] = Float.valueOf(value);
173           col++;
174         } catch (NumberFormatException e)
175         {
176           err = String.format(
177                   "Invalid score value '%s' at line %d column %d", value,
178                   lineNo, col);
179           throw new FileFormatException(err);
180         }
181       }
182       row++;
183     }
184
185     /*
186      * out of data - check we found enough
187      */
188     if (row < size)
189     {
190       err = String
191               .format("Expected %d rows of score data in score matrix but only found %d",
192                       size, row);
193       throw new FileFormatException(err);
194     }
195
196     /*
197      * If we get here, then name, alphabet and scores have been parsed successfully
198      */
199     sm = new ScoreMatrix(name, alphabet.toCharArray(), scores);
200     matrixName = name;
201
202     return sm;
203   }
204
205   public String getMatrixName()
206   {
207     return matrixName;
208   }
209 }