001 /**
002 * Copyright (c) 2010, SIB. All rights reserved.
003 *
004 * SIB (Swiss Institute of Bioinformatics) - http://www.isb-sib.ch Host -
005 * https://sourceforge.net/projects/javaprotlib/
006 *
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions are met:
009 * Redistributions of source code must retain the above copyright notice, this
010 * list of conditions and the following disclaimer. Redistributions in binary
011 * form must reproduce the above copyright notice, this list of conditions and
012 * the following disclaimer in the documentation and/or other materials provided
013 * with the distribution. Neither the name of the SIB/GENEBIO nor the names of
014 * its contributors may be used to endorse or promote products derived from this
015 * software without specific prior written permission.
016 *
017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
020 * ARE DISCLAIMED. IN NO EVENT SHALL SIB/GENEBIO BE LIABLE FOR ANY DIRECT,
021 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
024 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 */
028 package org.expasy.jpl.commons.base.cond;
029
030
031 import java.util.Arrays;
032 import java.util.HashSet;
033 import java.util.Set;
034 import org.apache.commons.collections15.Transformer;
035 import org.expasy.jpl.commons.base.TypedDataContainer;
036 import org.expasy.jpl.commons.base.TypedDatum;
037 import org.expasy.jpl.commons.base.builder.BuilderException;
038 import org.expasy.jpl.commons.base.builder.InstanceBuilder;
039 import org.expasy.jpl.commons.base.cond.operator.OperatorManager;
040 import org.expasy.jpl.commons.base.cond.operator.OperatorManager.InvalidOperatorException;
041 import org.expasy.jpl.commons.base.cond.operator.OperatorManager.InvalidOperatorRTException;
042 import org.expasy.jpl.commons.base.cond.operator.api.Operator;
043 import org.expasy.jpl.commons.base.cond.operator.impl.OperatorApproxEquals;
044 import org.expasy.jpl.commons.base.cond.operator.impl.OperatorBelongs;
045 import org.expasy.jpl.commons.base.cond.operator.impl.OperatorContains;
046 import org.expasy.jpl.commons.base.cond.operator.impl.OperatorEquals;
047 import org.expasy.jpl.commons.base.cond.operator.impl.OperatorGreaterThan;
048 import org.expasy.jpl.commons.base.cond.operator.impl.OperatorInter;
049 import org.expasy.jpl.commons.base.cond.operator.impl.OperatorLowerThan;
050 import org.expasy.jpl.commons.base.io.RegexConstants;
051
052
053 /**
054 * Conditional statement where a {@code T}-object may meet the condition on a
055 * {@code V}-value given an operator.
056 *
057 * @author nikitin
058 *
059 * @param <T> user's object that will be tested against <V>.
060 * @param <V> value to match to satisfy the condition.
061 *
062 * @version 1.0
063 */
064 @SuppressWarnings("unchecked")
065 public final class ConditionImpl<T, V> implements Condition<T> {
066
067 private static String DEFAULT_VARIABLE_NAME = "$_";
068
069 /** the name */
070 private String name;
071
072 /** the lvalue */
073 private String lvarName;
074
075 /** the rvalue */
076 private final V rvalue;
077
078 /** the operator */
079 private final Operator<Object, V> operator;
080
081 /** the not operator */
082 private final boolean not;
083
084 /** this mapper gives any object from T */
085 private final Transformer<T, ?> caster;
086
087 /**
088 * A builder to ease the construction of conditions
089 *
090 * @author nikitin
091 */
092 public static class Builder<T, V> implements
093 InstanceBuilder<ConditionImpl<T, V>> {
094
095 private static final Operator EQUALS = OperatorEquals.newInstance();
096
097 private static final Operator APPROX_EQUALS =
098 OperatorApproxEquals.newInstance();
099
100 /** default parameters */
101 private Operator operator = EQUALS;
102
103 private boolean not = false;
104 private String lvarName;
105 private final V rvalue;
106 private String name;
107
108 /** this teleporter lead from T to any object */
109 private Transformer<T, ?> caster;
110
111 public Builder(final V rvalue) {
112 this.rvalue = rvalue;
113 lvarName = DEFAULT_VARIABLE_NAME;
114 }
115
116 public Builder<T, V> name(String name) {
117 this.name = name;
118 return this;
119 }
120
121 /** the lvalue */
122 public Builder<T, V> lvarName(String lvalue) {
123 this.lvarName = lvalue;
124 return this;
125 }
126
127 /** set the path from T to ? */
128 public Builder<T, V> accessor(final Transformer<T, ?> caster) {
129 this.caster = caster;
130 return this;
131 }
132
133 public Builder<T, V> operator(final Operator operator) {
134 this.operator = operator;
135 return this;
136 }
137
138 public Builder<T, V> not() {
139 this.not = true;
140 return this;
141 }
142
143 /**
144 * Test the operator.
145 *
146 * @throws BuilderException if invalid operator.
147 */
148 private void checkOperator() throws BuilderException {
149
150 if (operator == null) {
151 throw new BuilderException("operator error: undefined operator");
152 } else if ((operator instanceof OperatorGreaterThan)
153 || (operator instanceof OperatorLowerThan)) {
154 if (!(rvalue instanceof Number)) {
155 throw new BuilderException(
156 "numeric operator error: the tested value has to be a number");
157 }
158 } else if (operator instanceof OperatorBelongs) {
159 if (!(rvalue instanceof Set)) {
160 throw new BuilderException("bad type value: " + rvalue
161 + " 'belongs' operator error - need a set value");
162 }
163 } else if (operator instanceof OperatorContains) {
164 if (rvalue instanceof Set) {
165 throw new BuilderException(
166 "set 'contains' operator error: need a non-set value");
167 }
168 } else if (operator instanceof OperatorInter) {
169 if (!(rvalue instanceof Set)) {
170 throw new BuilderException(
171 "set 'inter' operator error: need a set value");
172 }
173 } else if (operator instanceof OperatorApproxEquals) {
174 if (!(rvalue instanceof Number)) {
175 throw new BuilderException(
176 "numeric operator error: the tested value has to be a number");
177 } else if (((OperatorApproxEquals) operator).getDelta() == 0) {
178 operator = APPROX_EQUALS;
179 }
180 }
181 }
182
183 private String buildName() {
184 final StringBuilder sb = new StringBuilder();
185
186 if (lvarName != null) {
187 sb.append(lvarName);
188 } else {
189 sb.append(DEFAULT_VARIABLE_NAME);
190 }
191
192 sb.append(((not) ? "_not_" : "") + operator.getName());
193 sb.append(rvalue);
194
195 if (operator instanceof OperatorApproxEquals) {
196 OperatorApproxEquals approxOp = (OperatorApproxEquals) operator;
197
198 sb.append("_from_" + (((Double) rvalue) - approxOp.getDelta())
199 + "_to_" + (((Double) rvalue) + approxOp.getDelta()));
200 }
201
202 return sb.toString();
203 }
204
205 /**
206 * Build an instance of ConditionImpl after checking everything is ok
207 *
208 * @return an instance of ConditionImpl
209 */
210 public ConditionImpl<T, V> build() throws BuilderException {
211 checkOperator();
212
213 if (name == null) {
214 name = buildName();
215 }
216
217 return new ConditionImpl<T, V>(this);
218 }
219 }
220
221 /**
222 * Unique constructor given builder's parameters
223 *
224 * @param builder the builder to get parameters from
225 *
226 */
227 private ConditionImpl(final Builder<T, V> builder) {
228 this.name = builder.name;
229 this.operator = builder.operator;
230 this.not = builder.not;
231 this.lvarName = builder.lvarName;
232 this.rvalue = builder.rvalue;
233 this.caster = builder.caster;
234 }
235
236 /**
237 * {@inheritDoc}
238 */
239 public Operator<Object, V> getOperator() {
240 return operator;
241 }
242
243 /**
244 * {@inheritDoc}
245 */
246 public boolean isNotCondition() {
247 return not;
248 }
249
250 /**
251 * Test if {@code this} condition is valid for the given object
252 *
253 * @param object the object to test condition on
254 * @return true if valid condition
255 */
256 public boolean evaluate(final T object) {
257 Object lvalue = object;
258 boolean isTrue = false;
259
260 if (caster != null) {
261 lvalue = caster.transform(object);
262 }
263
264 if (!operator.isCompatibleOperands(lvalue, rvalue)) {
265 throw new InvalidOperatorRTException(operator.getClass()
266 .getSimpleName()
267 + " cannot process incompatible values "
268 + " "
269 + lvalue.getClass().getSimpleName()
270 + " {"
271 + lvalue
272 + "} versus "
273 + rvalue.getClass().getSimpleName()
274 + " {"
275 + rvalue + "}");
276 }
277
278 isTrue = operator.eval(lvalue, rvalue);
279
280 if (not) {
281 return !isTrue;
282 }
283 return isTrue;
284 }
285
286 // evaluate the variables provided by the manager
287 public boolean evaluate(final TypedDataContainer input) {
288 T value = null;
289
290 if (input == null || input.size() == 0) {
291 return false;
292 }
293
294 // make the evaluation for any anonymous condition too
295 if (lvarName.equals(DEFAULT_VARIABLE_NAME)) {
296 if (input.size() > 1) {
297 throw new ConditionRuntimeException(
298 "cannot choose from data input " + input);
299 }
300
301 TypedDatum uniqVar = input.getData().iterator().next();
302
303 // value compatibility tested in isTrue(T val)
304 value = (T) uniqVar.getValue();
305 }
306 // get variable name
307 else if (input.hasDatum(lvarName)) {
308 // value compatibility tested in isTrue(T val)
309 value = (T) input.getDatum(lvarName).getValue();
310 } else {
311 throw new ConditionRuntimeException("missing data input '"
312 + lvarName + "'");
313 }
314
315 return evaluate(value);
316 }
317
318 /**
319 * Trace tree expression stack status
320 */
321 public void printStackCommands() {
322 System.out.println(toString());
323 }
324
325 public Set<String> getDependentVariables() {
326 return new HashSet<String>(Arrays.asList(lvarName));
327 }
328
329 public V getROperand() {
330 return rvalue;
331 }
332
333 public static <T, V> String getStackCommands(ConditionImpl<T, V> cond,
334 V value) {
335 final StringBuilder sb = new StringBuilder();
336 Operator op = cond.getOperator();
337
338 if (cond.lvarName != null) {
339 sb.append(cond.lvarName);
340 sb.append(" " + ((cond.isNotCondition()) ? "!" : "") + op + " ");
341 sb.append(value);
342 } else {
343 sb.append("Condition tested against '"
344 + ((cond.getROperand() != null) ? cond.getROperand().getClass()
345 .getSimpleName() : "undef") + "': ");
346
347 sb.append("operator='" + ((cond.isNotCondition()) ? "!" : "") + op
348 + "', ");
349 sb.append("vs value " + value);
350 }
351 if (op instanceof OperatorApproxEquals) {
352 OperatorApproxEquals approxOp = (OperatorApproxEquals) op;
353
354 sb.append(" (in [" + ((Double) value - approxOp.getDelta()) + ", "
355 + ((Double) value + approxOp.getDelta()) + "])");
356 }
357
358 return sb.toString();
359 }
360
361 public String getName() {
362 return name;
363 }
364
365 /**
366 * Factory method for numerical condition types.
367 *
368 * @param operator the operator string.
369 * @param rvalue the rvalue string.
370 *
371 * @return a new instance of {@code ConditionImpl}
372 *
373 * @throws BuilderException
374 */
375 public static <T, V> ConditionImpl<T, V> valueOf(String lvalueName,
376 String operator, String rvalue) throws BuilderException {
377
378 OperatorManager manager = OperatorManager.getInstance();
379
380 // 1. localise and get instance of operator
381 Operator op;
382 try {
383 op = manager.getOperator(operator);
384 } catch (InvalidOperatorException e) {
385 throw new BuilderException(e.getMessage());
386 }
387
388 // 2. get rvalue
389 String rValueStr = rvalue;
390
391 Number rValue = null;
392
393 if (rValueStr.matches(RegexConstants.INTEGER)) {
394 rValue = Integer.parseInt(rValueStr);
395 } else if (rValueStr.matches(RegexConstants.REAL)) {
396 rValue = Double.parseDouble(rValueStr);
397 }
398
399 if (rValue != null) {
400 Builder builder = new Builder(rValue).operator(op);
401
402 if (lvalueName != null && lvalueName.length() > 0) {
403 builder.lvarName(lvalueName);
404 }
405
406 return builder.build();
407 } else {
408 throw new BuilderException(rvalue + ": bad rvalue format");
409 }
410
411 }
412
413 public static <T, V> ConditionImpl<T, V> valueOf(String operator,
414 String rvalue) throws BuilderException {
415
416 return valueOf(null, operator, rvalue);
417 }
418
419 /**
420 * Factory method for numerical condition types.
421 *
422 * @param condition the condition string to convert into ConditionImpl
423 * @return a new instance of {@code ConditionImpl}
424 *
425 * @throws OperatorNotFoundRTException
426 */
427 public static <T, V> ConditionImpl<T, V> valueOf(String condition)
428 throws BuilderException {
429 OperatorManager manager = OperatorManager.getInstance();
430
431 String[] lvalOpRval;
432 try {
433 lvalOpRval = manager.getRvalueOpLvalue(condition);
434 } catch (InvalidOperatorException e) {
435 throw new BuilderException(e.getMessage());
436 }
437
438 return valueOf(lvalOpRval[0], lvalOpRval[1], lvalOpRval[2]);
439 }
440
441 @Override
442 public String toString() {
443 return getStackCommands(this, this.rvalue);
444 }
445
446 /**
447 * Thrown when condition problem met.
448 *
449 * @author nikitin
450 *
451 * @version 1.0.0
452 *
453 */
454 public static class ConditionRuntimeException extends RuntimeException {
455
456 private static final long serialVersionUID = 1L;
457
458 public ConditionRuntimeException() {}
459
460 public ConditionRuntimeException(final String message) {
461 super(message);
462 }
463
464 public ConditionRuntimeException(final String message,
465 final Throwable cause) {
466 super(message, cause);
467 }
468
469 public ConditionRuntimeException(final Throwable cause) {
470 super(cause);
471 }
472 }
473 }