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