14a8585100a8d1e7317db8be28e7103f2c940651
[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 comparison
41    *           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 == null ? null : 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               || condition == Condition.NotPresent;
110     }
111     
112     String upper = val.toUpperCase().trim();
113     boolean matched = false;
114     switch(condition) {
115     case Matches:
116       matched = upper.equals(pattern);
117       break;
118     case NotMatches:
119       matched = !upper.equals(pattern);
120       break;
121     case Contains:
122       matched = upper.indexOf(pattern) > -1;
123       break;
124     case NotContains:
125       matched = upper.indexOf(pattern) == -1;
126       break;
127     case Present:
128       matched = true;
129       break;
130     default:
131       break;
132     }
133     return matched;
134   }
135
136   /**
137    * Applies a numerical comparison match condition
138    * 
139    * @param f
140    * @return
141    */
142   @SuppressWarnings("incomplete-switch")
143   boolean matches(float f)
144   {
145     if (!condition.isNumeric())
146     {
147       return matches(String.valueOf(f));
148     }
149     
150     boolean matched = false;
151     switch (condition) {
152     case LT:
153       matched = f < value;
154       break;
155     case LE:
156       matched = f <= value;
157       break;
158     case EQ:
159       matched = f == value;
160       break;
161     case NE:
162       matched = f != value;
163       break;
164     case GT:
165       matched = f > value;
166       break;
167     case GE:
168       matched = f >= value;
169       break;
170     default:
171       break;
172     }
173
174     return matched;
175   }
176
177   /**
178    * A simple hash function that guarantees that when two objects are equal,
179    * they have the same hashcode
180    */
181   @Override
182   public int hashCode()
183   {
184     return pattern.hashCode() + condition.hashCode() + (int) value;
185   }
186
187   /**
188    * equals is overridden so that we can safely remove Matcher objects from
189    * collections (e.g. delete an attribut match condition for a feature colour)
190    */
191   @Override
192   public boolean equals(Object obj)
193   {
194     if (obj == null || !(obj instanceof Matcher))
195     {
196       return false;
197     }
198     Matcher m = (Matcher) obj;
199     return condition == m.condition && value == m.value
200             && pattern.equals(m.pattern);
201   }
202
203   @Override
204   public Condition getCondition()
205   {
206     return condition;
207   }
208
209   @Override
210   public String getPattern()
211   {
212     return pattern;
213   }
214
215   @Override
216   public float getFloatValue()
217   {
218     return value;
219   }
220
221   @Override
222   public String toString()
223   {
224     StringBuilder sb = new StringBuilder();
225     sb.append(condition.name()).append(" ");
226     if (condition.isNumeric())
227     {
228       sb.append(pattern);
229     }
230     else
231     {
232       sb.append("'").append(pattern).append("'");
233     }
234
235     return sb.toString();
236   }
237 }