001    /*
002     * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003     * Copyright (C) 2011 NightLabs Consulting GmbH
004     *
005     * This program is free software: you can redistribute it and/or modify
006     * it under the terms of the GNU Affero General Public License as
007     * published by the Free Software Foundation, either version 3 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU Affero General Public License for more details.
014     *
015     * You should have received a copy of the GNU Affero General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    package org.cumulus4j.store.query.eval;
019    
020    import org.cumulus4j.store.model.FieldMeta;
021    import org.datanucleus.query.symbol.Symbol;
022    
023    /**
024     * <p>
025     * Descriptor specifying what kind of result is expected when a query is executed.
026     * This contains the information what candidates a query should search
027     * (usually "this" or a variable) as well as modifiers affecting the query
028     * (e.g. {@link #isNegated() negation}).
029     * </p>
030     *
031     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
032     */
033    public class ResultDescriptor
034    {
035            private Symbol symbol;
036            private Class<?> resultType;
037            private FieldMeta fieldMeta;
038            private boolean negated;
039    
040            /**
041             * Create a <code>ResultDescriptor</code>.
042             * @param symbol the symbol; must not be <code>null</code>.
043             * @param resultType the type of the searched candidates. This can be <code>null</code>,
044             * if {@link Symbol#getValueType()} is not <code>null</code>. If {@link Symbol#getValueType()}
045             * is not <code>null</code>, this argument is ignored.
046             */
047            public ResultDescriptor(Symbol symbol, Class<?> resultType)
048            {
049                    if (symbol == null)
050                            throw new IllegalArgumentException("symbol == null");
051    
052                    this.symbol = symbol;
053    
054                    if (symbol.getValueType() != null)
055                            this.resultType = symbol.getValueType();
056                    else
057                            this.resultType = resultType;
058    
059                    if (this.resultType == null)
060                            throw new IllegalArgumentException("resultType could not be determined!");
061            }
062    
063            /**
064             * Create a <code>ResultDescriptor</code>.
065             * @param symbol the symbol; must not be <code>null</code>.
066             * @param resultType the type of the searched candidates. This can be <code>null</code>,
067             * if {@link Symbol#getValueType()} is not <code>null</code>. If {@link Symbol#getValueType()}
068             * is not <code>null</code>, this argument is ignored.
069             * @param fieldMeta the field to be queried, if there is no FCO candidate. Must be
070             * <code>null</code>, if an FCO is searched.
071             */
072            public ResultDescriptor(Symbol symbol, Class<?> resultType, FieldMeta fieldMeta)
073            {
074                    this(symbol, resultType);
075                    this.fieldMeta = fieldMeta;
076            }
077    
078            private ResultDescriptor(Symbol symbol, Class<?> resultType, FieldMeta fieldMeta, boolean negated)
079            {
080                    this(symbol, resultType, fieldMeta);
081                    this.negated = negated;
082            }
083    
084            /**
085             * Get the symbol specifying what candidates are searched.
086             *
087             * @return the symbol; never <code>null</code>.
088             */
089            public Symbol getSymbol() {
090                    return symbol;
091            }
092    
093            /**
094             * Get the type of the searched candidates. Note, that they might be instances of a subclass.
095             * @return the type; never <code>null</code>.
096             */
097            public Class<?> getResultType() {
098                    return resultType;
099            }
100    
101            /**
102             * Get the {@link FieldMeta} to query, if there is no FCO candidate. For example, when
103             * querying for a joined <code>Set&lt;String&gt;.contains(variable)</code>.
104             * This is <code>null</code> when querying for an FCO (then the context is clear from the symbol).
105             * @return the {@link FieldMeta} to query or <code>null</code>.
106             */
107            public FieldMeta getFieldMeta() {
108                    return fieldMeta;
109            }
110    
111            /**
112             * <p>
113             * Whether the result is the negation of the actual criteria.
114             * </p>
115             * <p>
116             * It is quite expensive to evaluate a negation (JDOQL "!") by first querying the normal (non-negated)
117             * result and then negating it by querying ALL candidates and finally filtering the normal result
118             * out. Therefore, we instead push the negation down the expression-evaluator-tree into the leafs.
119             * Thus {@link NotExpressionEvaluator} simply calls {@link #negate()} and passes the negated
120             * <code>ResultDescriptor</code> down the evaluator-tree.
121             * All nodes in the tree therefore have to take this flag into account.
122             * </p>
123             *
124             * @return whether the result is the negation of the actual criteria.
125             */
126            public boolean isNegated() {
127                    return negated;
128            }
129    
130            /**
131             * Create a negation of this <code>ResultDescriptor</code>. The result will be a copy of this
132             * instance with all fields having the same value except for the {@link #isNegated() negated} flag
133             * which will have the opposite value.
134             * @return a negation of this <code>ResultDescriptor</code>.
135             */
136            public ResultDescriptor negate()
137            {
138                    return new ResultDescriptor(symbol, resultType, fieldMeta, !negated);
139            }
140    
141            @Override
142            public int hashCode() {
143                    final int prime = 31;
144                    int result = 1;
145                    result = prime * result + ((symbol == null) ? 0 : symbol.hashCode());
146                    result = prime * result + ((resultType == null) ? 0 : resultType.hashCode());
147                    result = prime * result + ((fieldMeta == null) ? 0 : fieldMeta.hashCode());
148                    result = prime * result + (negated ? 1231 : 1237);
149                    return result;
150            }
151    
152            @Override
153            public boolean equals(Object obj) {
154                    if (this == obj) return true;
155                    if (obj == null) return false;
156                    if (getClass() != obj.getClass())
157                            return false;
158    
159                    ResultDescriptor other = (ResultDescriptor) obj;
160                    return (
161                                    this.symbol == other.symbol || (this.symbol != null && this.symbol.equals(other.symbol)) &&
162                                    this.negated == other.negated &&
163                                    this.resultType == other.resultType || (this.resultType != null && this.resultType.equals(other.resultType)) &&
164                                    this.fieldMeta == other.fieldMeta || (this.fieldMeta != null && this.fieldMeta.equals(other.fieldMeta))
165                    );
166            }
167    }