View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.graph.query.model;
25  
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import net.jcip.annotations.Immutable;
34  import org.modeshape.common.i18n.I18n;
35  import org.modeshape.common.util.CheckArg;
36  import org.modeshape.common.util.HashCode;
37  import org.modeshape.common.util.ObjectUtil;
38  import org.modeshape.graph.GraphI18n;
39  
40  /**
41   * This object acts as a Set operator on multiple {@link QueryCommand queries}, such as performing UNION, INTERSECT, and EXCEPT
42   * operations.
43   * <p>
44   * The two {@link QueryCommand queries} are expected to have the same number and order of columns, and the corresponding columns
45   * types must be compatible.
46   * </p>
47   */
48  @Immutable
49  public class SetQuery implements QueryCommand {
50      private static final long serialVersionUID = 1L;
51  
52      public enum Operation implements Readable {
53          UNION("UNION"),
54          INTERSECT("INTERSECT"),
55          EXCEPT("EXCEPT");
56  
57          private static final Map<String, Operation> OPERATIONS_BY_SYMBOL;
58          static {
59              Map<String, Operation> opsBySymbol = new HashMap<String, Operation>();
60              for (Operation op : Operation.values()) {
61                  opsBySymbol.put(op.getSymbol(), op);
62              }
63              OPERATIONS_BY_SYMBOL = Collections.unmodifiableMap(opsBySymbol);
64          }
65  
66          private final String symbol;
67  
68          private Operation( String symbol ) {
69              this.symbol = symbol;
70          }
71  
72          /**
73           * @return symbol
74           */
75          public String getSymbol() {
76              return symbol;
77          }
78  
79          /**
80           * {@inheritDoc}
81           * 
82           * @see java.lang.Enum#toString()
83           */
84          @Override
85          public String toString() {
86              return symbol;
87          }
88  
89          public static Operation forSymbol( String symbol ) {
90              CheckArg.isNotNull(symbol, "symbol");
91              return OPERATIONS_BY_SYMBOL.get(symbol.toUpperCase());
92          }
93  
94          /**
95           * {@inheritDoc}
96           * 
97           * @see org.modeshape.graph.query.model.Readable#getString()
98           */
99          public String getString() {
100             return getSymbol();
101         }
102     }
103 
104     protected static boolean unionableColumns( List<? extends Column> left,
105                                                List<? extends Column> right ) {
106         // Check the column size first ...
107         if (left.size() != right.size()) return false;
108         // Same size, so check the columns ...
109         Iterator<? extends Column> leftIter = left.iterator();
110         Iterator<? extends Column> rightIter = right.iterator();
111         while (leftIter.hasNext() && rightIter.hasNext()) {
112             Column leftColumn = leftIter.next();
113             Column rightColumn = rightIter.next();
114             if (leftColumn == null || rightColumn == null) return false;
115         }
116         return leftIter.hasNext() == rightIter.hasNext();
117     }
118 
119     private final List<? extends Ordering> orderings;
120     private final Limit limits;
121     private final QueryCommand left;
122     private final QueryCommand right;
123     private final Operation operation;
124     private final boolean all;
125     private final int hc;
126 
127     /**
128      * Create a set query involving the supplied left- and right-hand-side queries.
129      * 
130      * @param left the left-hand-side query being combined
131      * @param operation the set operation
132      * @param right the right-hand-side query being combined
133      * @param all true if all of the results should be included
134      * @throws IllegalArgumentException if the left-hand-side query, right-hand-side query, or operation are null
135      */
136     public SetQuery( QueryCommand left,
137                      Operation operation,
138                      QueryCommand right,
139                      boolean all ) {
140         CheckArg.isNotNull(left, "left");
141         CheckArg.isNotNull(right, "right");
142         CheckArg.isNotNull(operation, "operation");
143         if (!unionableColumns(left.columns(), right.columns())) {
144             I18n msg = GraphI18n.leftAndRightQueriesInSetQueryMustHaveUnionableColumns;
145             throw new IllegalArgumentException(msg.text(left.columns(), right.columns()));
146         }
147         this.left = left;
148         this.right = right;
149         this.operation = operation;
150         this.all = all;
151         this.orderings = Collections.<Ordering>emptyList();
152         this.limits = Limit.NONE;
153         this.hc = HashCode.compute(this.left, this.right, this.operation);
154     }
155 
156     /**
157      * Create a set query involving the supplied left- and right-hand-side queries.
158      * 
159      * @param left the left-hand-side query being combined
160      * @param operation the set operation
161      * @param right the right-hand-side query being combined
162      * @param all true if all of the results should be included
163      * @param orderings the specification of the order of the result rows, or null if the results need not be ordered
164      * @param limit the limit for the result rows, or null if there are no limits
165      * @throws IllegalArgumentException if the left-hand-side query, right-hand-side query, or operation are null
166      */
167     public SetQuery( QueryCommand left,
168                      Operation operation,
169                      QueryCommand right,
170                      boolean all,
171                      List<? extends Ordering> orderings,
172                      Limit limit ) {
173         CheckArg.isNotNull(left, "left");
174         CheckArg.isNotNull(right, "right");
175         CheckArg.isNotNull(operation, "operation");
176         if (!unionableColumns(left.columns(), right.columns())) {
177             I18n msg = GraphI18n.leftAndRightQueriesInSetQueryMustHaveUnionableColumns;
178             throw new IllegalArgumentException(msg.text(left.columns(), right.columns()));
179         }
180         this.left = left;
181         this.right = right;
182         this.operation = operation;
183         this.all = all;
184         this.orderings = orderings != null ? orderings : Collections.<Ordering>emptyList();
185         this.limits = limit != null ? limit : Limit.NONE;
186         this.hc = HashCode.compute(this.left, this.right, this.operation);
187     }
188 
189     /**
190      * {@inheritDoc}
191      * 
192      * @see org.modeshape.graph.query.model.QueryCommand#columns()
193      */
194     public List<? extends Column> columns() {
195         return left.columns(); // equivalent to right columns
196     }
197 
198     /**
199      * {@inheritDoc}
200      * 
201      * @see org.modeshape.graph.query.model.QueryCommand#limits()
202      */
203     public Limit limits() {
204         return limits;
205     }
206 
207     /**
208      * {@inheritDoc}
209      * 
210      * @see org.modeshape.graph.query.model.QueryCommand#orderings()
211      */
212     public List<? extends Ordering> orderings() {
213         return orderings;
214     }
215 
216     /**
217      * Get the left-hand query.
218      * 
219      * @return the left-hand query; never null
220      */
221     public QueryCommand left() {
222         return left;
223     }
224 
225     /**
226      * Get the right-hand query.
227      * 
228      * @return the right-hand query; never null
229      */
230     public QueryCommand right() {
231         return right;
232     }
233 
234     /**
235      * Get the set operation for this query.
236      * 
237      * @return the operation; never null
238      */
239     public final Operation operation() {
240         return operation;
241     }
242 
243     /**
244      * Return whether this set query is a 'UNION ALL' or 'INTERSECT ALL' or 'EXCEPT ALL' query.
245      * 
246      * @return true if this is an 'ALL' query, or false otherwise
247      */
248     public final boolean isAll() {
249         return all;
250     }
251 
252     /**
253      * {@inheritDoc}
254      * 
255      * @see java.lang.Object#toString()
256      */
257     @Override
258     public String toString() {
259         return Visitors.readable(this);
260     }
261 
262     /**
263      * {@inheritDoc}
264      * 
265      * @see java.lang.Object#hashCode()
266      */
267     @Override
268     public int hashCode() {
269         return hc;
270     }
271 
272     /**
273      * {@inheritDoc}
274      * 
275      * @see java.lang.Object#equals(java.lang.Object)
276      */
277     @Override
278     public boolean equals( Object obj ) {
279         if (obj == this) return true;
280         if (obj instanceof SetQuery) {
281             SetQuery that = (SetQuery)obj;
282             if (this.hc != that.hc) return false;
283             if (this.operation != that.operation) return false;
284             if (!this.left.equals(that.left)) return false;
285             if (!this.right.equals(that.right)) return false;
286             if (!ObjectUtil.isEqualWithNulls(this.limits(), that.limits())) return false;
287             if (!ObjectUtil.isEqualWithNulls(this.orderings(), that.orderings())) return false;
288             return true;
289         }
290         return false;
291     }
292 
293     /**
294      * {@inheritDoc}
295      * 
296      * @see org.modeshape.graph.query.model.Visitable#accept(org.modeshape.graph.query.model.Visitor)
297      */
298     public void accept( Visitor visitor ) {
299         visitor.visit(this);
300     }
301 
302     public SetQuery withLimit( int rowLimit ) {
303         if (limits().rowLimit() == rowLimit) return this; // nothing to change
304         return new SetQuery(left, operation, right, all, orderings(), limits().withRowLimit(rowLimit));
305     }
306 
307     public SetQuery withOffset( int offset ) {
308         if (limits().offset() == offset) return this; // nothing to change
309         return new SetQuery(left, operation, right, all, orderings(), limits().withOffset(offset));
310     }
311 
312     public SetQuery adding( Ordering... orderings ) {
313         List<Ordering> newOrderings = null;
314         if (this.orderings() != null) {
315             newOrderings = new ArrayList<Ordering>(orderings());
316             for (Ordering ordering : orderings) {
317                 newOrderings.add(ordering);
318             }
319         } else {
320             newOrderings = Arrays.asList(orderings);
321         }
322         return new SetQuery(left, operation, right, all, newOrderings, limits());
323     }
324 }