a213a17a96ba71aebd54382d8de33fc81b184acb
[jalview.git] / src / jalview / util / matcher / Matcher.java
1 package jalview.util.matcher;
2
3 import java.util.Objects;
4 import java.util.regex.Pattern;
5
6 /**
7  * A bean to describe one attribute-based filter
8  */
9 public class Matcher implements MatcherI
10 {
11   /*
12    * the comparison condition
13    */
14   Condition condition;
15
16   /*
17    * the string value (upper-cased), or the regex, to compare to
18    * also holds the string form of float value if a numeric condition
19    */
20   String pattern;
21
22   /*
23    * the compiled regex if using a pattern match condition
24    * (reserved for possible future enhancement)
25    */
26   Pattern regexPattern;
27
28   /*
29    * the value to compare to for a numerical condition
30    */
31   float value;
32
33   /**
34    * Constructor
35    * 
36    * @param cond
37    * @param compareTo
38    * @return
39    * @throws NumberFormatException
40    *           if a numerical condition is specified with a non-numeric
41    *           comparision value
42    * @throws NullPointerException
43    *           if a null condition or comparison string is specified
44    */
45   public Matcher(Condition cond, String compareTo)
46   {
47     condition = cond;
48     if (cond.isNumeric())
49     {
50       value = Float.valueOf(compareTo);
51       pattern = String.valueOf(value);
52     }
53     else
54     {
55       // pattern matches will be non-case-sensitive
56       pattern = compareTo.toUpperCase();
57     }
58
59     // if we add regex conditions (e.g. matchesPattern), then
60     // pattern should hold the raw regex, and
61     // regexPattern = Pattern.compile(compareTo);
62   }
63
64   /**
65    * Constructor for a numerical match condition. Note that if a string
66    * comparison condition is specified, this will be converted to a comparison
67    * with the float value as string
68    * 
69    * @param cond
70    * @param compareTo
71    */
72   public Matcher(Condition cond, float compareTo)
73   {
74     Objects.requireNonNull(cond);
75     condition = cond;
76     value = compareTo;
77     pattern = String.valueOf(compareTo).toUpperCase();
78   }
79
80   /**
81    * {@inheritDoc}
82    */
83   @SuppressWarnings("incomplete-switch")
84   @Override
85   public boolean matches(String val)
86   {
87     if (condition.isNumeric())
88     {
89       try
90       {
91         /*
92          * treat a null value (no such attribute) as
93          * failing any numerical filter condition
94          */
95         return val == null ? false : matches(Float.valueOf(val));
96       } catch (NumberFormatException e)
97       {
98         return false;
99       }
100     }
101     
102     /*
103      * a null value matches a negative condition, fails a positive test
104      */
105     if (val == null)
106     {
107       return condition == Condition.NotContains
108               || condition == Condition.NotMatches;
109     }
110     
111     String upper = val.toUpperCase().trim();
112     boolean matched = false;
113     switch(condition) {
114     case Matches:
115       matched = upper.equals(pattern);
116       break;
117     case NotMatches:
118       matched = !upper.equals(pattern);
119       break;
120     case Contains:
121       matched = upper.indexOf(pattern) > -1;
122       break;
123     case NotContains:
124       matched = upper.indexOf(pattern) == -1;
125       break;
126     }
127     return matched;
128   }
129
130   /**
131    * Applies a numerical comparison match condition
132    * 
133    * @param f
134    * @return
135    */
136   @SuppressWarnings("incomplete-switch")
137   boolean matches(float f)
138   {
139     if (!condition.isNumeric())
140     {
141       return matches(String.valueOf(f));
142     }
143     
144     boolean matched = false;
145     switch (condition) {
146     case LT:
147       matched = f < value;
148       break;
149     case LE:
150       matched = f <= value;
151       break;
152     case EQ:
153       matched = f == value;
154       break;
155     case NE:
156       matched = f != value;
157       break;
158     case GT:
159       matched = f > value;
160       break;
161     case GE:
162       matched = f >= value;
163       break;
164     }
165
166     return matched;
167   }
168
169   /**
170    * A simple hash function that guarantees that when two objects are equal,
171    * they have the same hashcode
172    */
173   @Override
174   public int hashCode()
175   {
176     return pattern.hashCode() + condition.hashCode() + (int) value;
177   }
178
179   /**
180    * equals is overridden so that we can safely remove Matcher objects from
181    * collections (e.g. delete an attribut match condition for a feature colour)
182    */
183   @Override
184   public boolean equals(Object obj)
185   {
186     if (obj == null || !(obj instanceof Matcher))
187     {
188       return false;
189     }
190     Matcher m = (Matcher) obj;
191     return condition == m.condition && value == m.value
192             && pattern.equals(m.pattern);
193   }
194
195   @Override
196   public Condition getCondition()
197   {
198     return condition;
199   }
200
201   @Override
202   public String getPattern()
203   {
204     return pattern;
205   }
206
207   @Override
208   public float getFloatValue()
209   {
210     return value;
211   }
212
213   @Override
214   public String toString()
215   {
216     StringBuilder sb = new StringBuilder();
217     sb.append(condition.name()).append(" ");
218     if (condition.isNumeric())
219     {
220       sb.append(pattern);
221     }
222     else
223     {
224       sb.append("'").append(pattern).append("'");
225     }
226
227     return sb.toString();
228   }
229 }