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    }