1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.jdbc.kernel.exps;
20
21 import java.io.Serializable;
22 import java.sql.SQLException;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.ListIterator;
26
27 import org.apache.commons.lang.ObjectUtils;
28 import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
29 import org.apache.openjpa.jdbc.meta.ClassMapping;
30 import org.apache.openjpa.jdbc.meta.FieldMapping;
31 import org.apache.openjpa.jdbc.meta.ValueMapping;
32 import org.apache.openjpa.jdbc.schema.Column;
33 import org.apache.openjpa.jdbc.schema.ForeignKey;
34 import org.apache.openjpa.jdbc.schema.Schemas;
35 import org.apache.openjpa.jdbc.sql.Joins;
36 import org.apache.openjpa.jdbc.sql.Result;
37 import org.apache.openjpa.jdbc.sql.SQLBuffer;
38 import org.apache.openjpa.jdbc.sql.Select;
39 import org.apache.openjpa.kernel.Filters;
40 import org.apache.openjpa.lib.util.Localizer;
41 import org.apache.openjpa.meta.ClassMetaData;
42 import org.apache.openjpa.meta.FieldMetaData;
43 import org.apache.openjpa.meta.JavaTypes;
44 import org.apache.openjpa.meta.XMLMetaData;
45 import org.apache.openjpa.util.UserException;
46
47 /**
48 * A path represents a traversal into fields of a candidate object.
49 *
50 * @author Abe White
51 */
52 public class PCPath
53 extends AbstractVal
54 implements JDBCPath {
55
56 private static final int PATH = 0;
57 private static final int BOUND_VAR = 1;
58 private static final int UNBOUND_VAR = 2;
59 private static final int UNACCESSED_VAR = 3;
60 private static final int XPATH = 4;
61
62 private static final Localizer _loc = Localizer.forPackage(PCPath.class);
63
64 private final ClassMapping _candidate;
65 private ClassMapping _class = null;
66 private LinkedList _actions = null;
67 private boolean _key = false;
68 private int _type = PATH;
69 private String _varName = null;
70 private Class _cast = null;
71 private boolean _cid = false;
72 private FieldMetaData _xmlfield = null;
73
74 /**
75 * Return a path starting with the 'this' ptr.
76 */
77 public PCPath(ClassMapping type) {
78 _candidate = type;
79 }
80
81 /**
82 * Return a path starting from the given variable.
83 */
84 public PCPath(ClassMapping candidate, Variable var) {
85 _candidate = candidate;
86 _actions = new LinkedList();
87
88 PCPath other = var.getPCPath();
89 Action action = new Action();
90 if (other == null) {
91 _type = UNBOUND_VAR;
92 action.op = Action.UNBOUND_VAR;
93 action.data = var;
94 } else {
95 // bound variable; copy path
96 _type = UNACCESSED_VAR;
97 _actions.addAll(other._actions);
98 _key = other._key;
99
100 action.op = Action.VAR;
101 action.data = var.getName();
102 }
103 _actions.add(action);
104 _cast = var.getType(); // initial type is var type
105 }
106
107 /**
108 * Return a path starting from the given subquery.
109 */
110 public PCPath(SubQ sub) {
111 _candidate = sub.getCandidate();
112 _actions = new LinkedList();
113
114 Action action = new Action();
115 action.op = Action.SUBQUERY;
116 action.data = sub.getCandidateAlias();
117 _actions.add(action);
118 _cast = sub.getType(); // initial type is subquery type
119 _varName = sub.getCandidateAlias();
120 }
121
122 /**
123 * Set the path as a binding of the given variable.
124 */
125 public void addVariableAction(Variable var) {
126 _varName = var.getName();
127 }
128
129 /**
130 * Return true if this is a bound variable that has not been accessed
131 * after binding. Useful for filters like
132 * "coll.contains (var) && var == null", which should really
133 * just act like "coll.contains (null)".
134 */
135 public boolean isUnaccessedVariable() {
136 return _type == UNACCESSED_VAR;
137 }
138
139 /**
140 * Return whether this is a path involving a variable.
141 */
142 public boolean isVariablePath() {
143 return _type != PATH;
144 }
145
146 /**
147 * If this path is part of a contains clause, then alias it to the
148 * proper contains id before initialization.
149 */
150 public synchronized void setContainsId(String id) {
151 if (_cid)
152 return;
153
154 // treat it just like a unique variable
155 Action action = new Action();
156 action.op = Action.VAR;
157 action.data = id;
158 if (_actions == null)
159 _actions = new LinkedList();
160 _actions.add(action);
161 _cid = true;
162 }
163
164 public ClassMetaData getMetaData() {
165 return _class;
166 }
167
168 public void setMetaData(ClassMetaData meta) {
169 _class = (ClassMapping) meta;
170 }
171
172 public boolean isKey() {
173 return _key;
174 }
175
176 public boolean isXPath() {
177 return _type == XPATH;
178 }
179
180 public String getXPath() {
181 StringBuffer xpath = new StringBuffer();
182 Action action;
183 Iterator itr = _actions.iterator();
184
185 // Skip variable actions since they are not part of the xpath
186 // until we reach the first xpath action.
187 // The first xpath action maps to the root of an xml document.
188 do
189 action = (Action) itr.next();
190 while (action.op != Action.GET_XPATH);
191
192 // Skip XmlRootElement:
193 // We can't rely on the accuracy of the name of the root element,
194 // because it could be set to some default by JAXB XML Binding.
195 // The caller(DBDictionary) should start with "/*" or "/*/",
196 // we build the remaining xpath that follows the root element.
197 while (itr.hasNext()) {
198 action = (Action) itr.next();
199 if (((XMLMetaData) action.data).getXmlname() != null)
200 xpath.append(((XMLMetaData) action.data).getXmlname());
201 else
202 xpath.append("*");
203 if (itr.hasNext())
204 xpath.append("/");
205 }
206 return xpath.toString();
207 }
208
209 public String getPath() {
210 if (_actions == null)
211 return (_varName == null) ? "" : _varName + ".";
212
213 StringBuffer path = new StringBuffer();
214 Action action;
215 for (Iterator itr = _actions.iterator(); itr.hasNext();) {
216 action = (Action) itr.next();
217 if (action.op == Action.VAR || action.op == Action.SUBQUERY)
218 path.append(action.data);
219 else if (action.op == Action.UNBOUND_VAR)
220 path.append(((Variable) action.data).getName());
221 else
222 path.append(((FieldMapping) action.data).getName());
223 path.append('.');
224 }
225 if (_varName != null)
226 path.append(_varName).append('.');
227 return path.toString();
228 }
229
230 public ClassMapping getClassMapping(ExpState state) {
231 PathExpState pstate = (PathExpState) state;
232 if (pstate.field == null)
233 return _class;
234 if (_key) {
235 if (pstate.field.getKey().getTypeCode() == JavaTypes.PC)
236 return pstate.field.getKeyMapping().getTypeMapping();
237 return null;
238 }
239 if (pstate.field.getElement().getTypeCode() == JavaTypes.PC)
240 return pstate.field.getElementMapping().getTypeMapping();
241 if (pstate.field.getTypeCode() == JavaTypes.PC)
242 return pstate.field.getTypeMapping();
243 return null;
244 }
245
246 public FieldMapping getFieldMapping(ExpState state) {
247 return ((PathExpState) state).field;
248 }
249
250 public Column[] getColumns(ExpState state) {
251 PathExpState pstate = (PathExpState) state;
252 if (pstate.cols == null)
253 pstate.cols = calculateColumns(pstate);
254 return pstate.cols;
255 }
256
257 /**
258 * The columns used by this path.
259 */
260 private Column[] calculateColumns(PathExpState pstate) {
261 if (_key) {
262 if (!pstate.joinedRel
263 && pstate.field.getKey().getValueMappedBy() != null)
264 joinRelation(pstate, _key, false, false);
265 else if (pstate.joinedRel
266 && pstate.field.getKey().getTypeCode() == JavaTypes.PC)
267 return pstate.field.getKeyMapping().getTypeMapping().
268 getPrimaryKeyColumns();
269 return pstate.field.getKeyMapping().getColumns();
270 }
271 if (pstate.field != null) {
272 switch (pstate.field.getTypeCode()) {
273 case JavaTypes.MAP:
274 case JavaTypes.ARRAY:
275 case JavaTypes.COLLECTION:
276 ValueMapping elem = pstate.field.getElementMapping();
277 if (pstate.joinedRel && elem.getTypeCode() == JavaTypes.PC)
278 return elem.getTypeMapping().getPrimaryKeyColumns();
279 if (elem.getColumns().length > 0)
280 return elem.getColumns();
281 return pstate.field.getColumns();
282 case JavaTypes.PC:
283 if (pstate.joinedRel)
284 return pstate.field.getTypeMapping().
285 getPrimaryKeyColumns();
286 return pstate.field.getColumns();
287 default:
288 return pstate.field.getColumns();
289 }
290 }
291 return (_class == null) ? Schemas.EMPTY_COLUMNS
292 : _class.getPrimaryKeyColumns();
293 }
294
295 public boolean isVariable() {
296 if (_actions == null)
297 return false;
298 Action action = (Action) _actions.getLast();
299 return action.op == Action.UNBOUND_VAR || action.op == Action.VAR;
300 }
301
302 public void get(FieldMetaData field, boolean nullTraversal) {
303 if (_actions == null)
304 _actions = new LinkedList();
305 Action action = new Action();
306 action.op = (nullTraversal) ? Action.GET_OUTER : Action.GET;
307 action.data = field;
308 _actions.add(action);
309 if (_type == UNACCESSED_VAR)
310 _type = BOUND_VAR;
311 _cast = null;
312 _key = false;
313 }
314
315 public void get(FieldMetaData fmd, XMLMetaData meta) {
316 if (_actions == null)
317 _actions = new LinkedList();
318 Action action = new Action();
319 action.op = Action.GET_XPATH;
320 action.data = meta;
321 _actions.add(action);
322 _cast = null;
323 _key = false;
324 _type = XPATH;
325 _xmlfield = fmd;
326 }
327
328 public void get(XMLMetaData meta, String name) {
329 Action action = new Action();
330 action.op = Action.GET_XPATH;
331 action.data = meta.getFieldMapping(name);
332 _actions.add(action);
333 _cast = null;
334 _key = false;
335 _type = XPATH;
336 }
337
338 public XMLMetaData getXmlMapping() {
339 Action act = (Action) _actions.getLast();
340 if (act != null)
341 return (XMLMetaData) act.data;
342 return null;
343 }
344
345 public synchronized void getKey() {
346 if (_cid)
347 return;
348
349 // change the last action to a get key
350 Action action = (Action) _actions.getLast();
351 action.op = Action.GET_KEY;
352 _cast = null;
353 _key = true;
354 }
355
356 public FieldMetaData last() {
357 Action act = lastFieldAction();
358 return (act == null) ? null : isXPath() ? _xmlfield :
359 (FieldMetaData) act.data;
360 }
361
362 /**
363 * Return the last action that gets a field.
364 */
365 private Action lastFieldAction() {
366 if (_actions == null)
367 return null;
368
369 if (isXPath())
370 return (Action) _actions.getLast();
371
372 ListIterator itr = _actions.listIterator(_actions.size());
373 Action prev;
374 while (itr.hasPrevious()) {
375 prev = (Action) itr.previous();
376 if (prev.op == Action.GET || prev.op == Action.GET_OUTER
377 || prev.op == Action.GET_KEY)
378 return prev;
379 }
380 return null;
381 }
382
383 public Class getType() {
384 if (_cast != null)
385 return _cast;
386 Action act = lastFieldAction();
387 if (act != null && act.op == Action.GET_XPATH)
388 return ((XMLMetaData) act.data).getType();
389
390 FieldMetaData fld = (act == null) ? null : (FieldMetaData) act.data;
391 boolean key = act != null && act.op == Action.GET_KEY;
392 if (fld != null) {
393 switch (fld.getDeclaredTypeCode()) {
394 case JavaTypes.ARRAY:
395 if (fld.getDeclaredType() == byte[].class
396 || fld.getDeclaredType() == Byte[].class
397 || fld.getDeclaredType() == char[].class
398 || fld.getDeclaredType() == Character[].class)
399 return fld.getDeclaredType();
400 return fld.getElement().getDeclaredType();
401 case JavaTypes.MAP:
402 if (key)
403 return fld.getKey().getDeclaredType();
404 return fld.getElement().getDeclaredType();
405 case JavaTypes.COLLECTION:
406 return fld.getElement().getDeclaredType();
407 default:
408 return fld.getDeclaredType();
409 }
410 }
411 if (_class != null)
412 return _class.getDescribedType();
413 return Object.class;
414 }
415
416 public void setImplicitType(Class type) {
417 _cast = type;
418 }
419
420 public ExpState initialize(Select sel, ExpContext ctx, int flags) {
421 PathExpState pstate = new PathExpState(sel.newJoins());
422 boolean key = false;
423 boolean forceOuter = false;
424 ClassMapping rel = _candidate;
425
426 // iterate to the final field
427 ClassMapping owner;
428 ClassMapping from, to;
429 Action action;
430 Variable var;
431 Iterator itr = (_actions == null) ? null : _actions.iterator();
432 FieldMapping field;
433 while (itr != null && itr.hasNext()) {
434 action = (Action) itr.next();
435
436 // treat subqueries like variables for alias generation purposes
437 if (action.op == Action.VAR)
438 pstate.joins = pstate.joins.setVariable((String) action.data);
439 else if (action.op == Action.SUBQUERY)
440 pstate.joins = pstate.joins.setSubselect((String) action.data);
441 else if (action.op == Action.UNBOUND_VAR) {
442 // unbound vars are cross-joined to the candidate table
443 var = (Variable) action.data;
444 rel = (ClassMapping) var.getMetaData();
445 pstate.joins = pstate.joins.setVariable(var.getName());
446 pstate.joins = pstate.joins.crossJoin(_candidate.getTable(),
447 rel.getTable());
448 } else {
449 // move past the previous field, if any
450 field = (action.op == Action.GET_XPATH) ? (FieldMapping) _xmlfield :
451 (FieldMapping) action.data;
452 if (pstate.field != null) {
453 // if this is the second-to-last field and the last is
454 // the related field this field joins to, no need to
455 // traverse: just use this field's fk columns
456 if (!itr.hasNext() && (flags & JOIN_REL) == 0
457 && isJoinedField(pstate.field, key, field)) {
458 pstate.cmpfield = field;
459 break;
460 }
461 rel = traverseField(pstate, key, forceOuter, false);
462 }
463
464 // mark if the next traversal should go through
465 // the key rather than value
466 key = action.op == Action.GET_KEY;
467 forceOuter |= action.op == Action.GET_OUTER;
468
469 // get mapping for the current field
470 pstate.field = field;
471 owner = pstate.field.getDefiningMapping();
472 if (pstate.field.getManagement()
473 != FieldMapping.MANAGE_PERSISTENT)
474 throw new UserException(_loc.get("non-pers-field",
475 pstate.field));
476
477 // find the most-derived type between the declared relation
478 // type and the field's owner, and join from that type to
479 // the lesser derived type
480 if (rel != owner && rel != null) {
481 if (rel.getDescribedType().isAssignableFrom
482 (owner.getDescribedType())) {
483 from = owner;
484 to = rel;
485 } else {
486 from = rel;
487 to = owner;
488 }
489
490 for (; from != null && from != to;
491 from = from.getJoinablePCSuperclassMapping())
492 pstate.joins = from.joinSuperclass(pstate.joins, false);
493 }
494 // nothing more to do from here on as we encountered an xpath action
495 if (action.op == Action.GET_XPATH)
496 break;
497 }
498 }
499 if (_varName != null)
500 pstate.joins = pstate.joins.setVariable(_varName);
501
502 // if we're not comparing to null or doing an isEmpty, then
503 // join into the data on the final field; obviously we can't do these
504 // joins when comparing to null b/c the whole purpose is to see
505 // whether the joins even exist
506 if ((flags & NULL_CMP) == 0)
507 traverseField(pstate, key, forceOuter, true);
508 pstate.joinedRel = false;
509 if ((flags & JOIN_REL) != 0)
510 joinRelation(pstate, key, forceOuter || (flags & FORCE_OUTER) != 0,
511 false);
512 return pstate;
513 }
514
515 /**
516 * Return whether the given source field joins to the given target field.
517 */
518 private static boolean isJoinedField(FieldMapping src, boolean key,
519 FieldMapping target) {
520 ValueMapping vm;
521 switch (src.getTypeCode()) {
522 case JavaTypes.ARRAY:
523 case JavaTypes.COLLECTION:
524 vm = src.getElementMapping();
525 break;
526 case JavaTypes.MAP:
527 vm = (key) ? src.getKeyMapping() : src.getElementMapping();
528 break;
529 default:
530 vm = src;
531 }
532 if (vm.getJoinDirection() != ValueMapping.JOIN_FORWARD)
533 return false;
534 ForeignKey fk = vm.getForeignKey();
535 if (fk == null)
536 return false;
537
538 // foreign key must join to target columns
539 Column[] rels = fk.getColumns();
540 Column[] pks = target.getColumns();
541 if (rels.length != pks.length)
542 return false;
543 for (int i = 0; i < rels.length; i++)
544 if (fk.getPrimaryKeyColumn(rels[i]) != pks[i])
545 return false;
546 return true;
547 }
548
549 /**
550 * Expression state.
551 */
552 public static class PathExpState
553 extends ExpState {
554
555 public FieldMapping field = null;
556 public FieldMapping cmpfield = null;
557 public Column[] cols = null;
558 public boolean joinedRel = false;
559
560 public PathExpState(Joins joins) {
561 super(joins);
562 }
563 }
564
565 /**
566 * Traverse into the previous field of a relation path.
567 *
568 * @param last whether this is the last field in the path
569 * @return the mapping of the related type, or null
570 */
571 private ClassMapping traverseField(PathExpState pstate, boolean key,
572 boolean forceOuter, boolean last) {
573 if (pstate.field == null)
574 return null;
575
576 // traverse into field value
577 if (key)
578 pstate.joins = pstate.field.joinKey(pstate.joins, forceOuter);
579 else
580 pstate.joins = pstate.field.join(pstate.joins, forceOuter);
581
582 // if this isn't the last field, traverse into the relation
583 if (!last)
584 joinRelation(pstate, key, forceOuter, true);
585
586 // return the maping of the related type, if any
587 if (key)
588 return pstate.field.getKeyMapping().getTypeMapping();
589 if (pstate.field.getElement().getTypeCode() == JavaTypes.PC)
590 return pstate.field.getElementMapping().getTypeMapping();
591 return pstate.field.getTypeMapping();
592 }
593
594 /**
595 * Join into the relation represented by the current field, if any.
596 */
597 private void joinRelation(PathExpState pstate, boolean key,
598 boolean forceOuter, boolean traverse) {
599 if (pstate.field == null)
600 return;
601 if (key)
602 pstate.joins = pstate.field.joinKeyRelation(pstate.joins,
603 forceOuter, traverse);
604 else
605 pstate.joins = pstate.field.joinRelation(pstate.joins, forceOuter,
606 traverse);
607 pstate.joinedRel = true;
608 }
609
610 public Object toDataStoreValue(Select sel, ExpContext ctx, ExpState state,
611 Object val) {
612 PathExpState pstate = (PathExpState) state;
613 FieldMapping field = (pstate.cmpfield != null) ? pstate.cmpfield
614 : pstate.field;
615 if (isXPath())
616 return val;
617 if (field != null) {
618 if (_key)
619 return field.toKeyDataStoreValue(val, ctx.store);
620 if (field.getElement().getDeclaredTypeCode() != JavaTypes.OBJECT)
621 return field.toDataStoreValue(val, ctx.store);
622
623 val = field.getExternalValue(val, ctx.store.getContext());
624 return field.toDataStoreValue(val, ctx.store);
625 }
626 return _class.toDataStoreValue(val, _class.getPrimaryKeyColumns(),
627 ctx.store);
628 }
629
630 public void select(Select sel, ExpContext ctx, ExpState state,
631 boolean pks) {
632 selectColumns(sel, ctx, state, pks);
633 }
634
635 public void selectColumns(Select sel, ExpContext ctx, ExpState state,
636 boolean pks) {
637 ClassMapping mapping = getClassMapping(state);
638 PathExpState pstate = (PathExpState) state;
639 if (mapping == null || !pstate.joinedRel)
640 sel.select(getColumns(state), pstate.joins);
641 else if (pks)
642 sel.select(mapping.getPrimaryKeyColumns(), pstate.joins);
643 else {
644 // select the mapping; allow any subs because we know this must
645 // be either a relation, in which case it will already be
646 // constrained by the joins, or 'this', in which case the
647 // JDBCExpressionFactory takes care of adding class conditions for
648 // the candidate class on the select
649 int subs = (_type == UNBOUND_VAR) ? Select.SUBS_JOINABLE
650 : Select.SUBS_ANY_JOINABLE;
651 sel.select(mapping, subs, ctx.store, ctx.fetch,
652 JDBCFetchConfiguration.EAGER_NONE, sel.outer(pstate.joins));
653 }
654 }
655
656 public void groupBy(Select sel, ExpContext ctx, ExpState state) {
657 ClassMapping mapping = getClassMapping(state);
658 PathExpState pstate = (PathExpState) state;
659 if (mapping == null || !pstate.joinedRel)
660 sel.groupBy(getColumns(state), sel.outer(pstate.joins));
661 else {
662 int subs = (_type == UNBOUND_VAR) ? Select.SUBS_JOINABLE
663 : Select.SUBS_ANY_JOINABLE;
664 sel.groupBy(mapping, subs, ctx.store, ctx.fetch,
665 sel.outer(pstate.joins));
666 }
667 }
668
669 public void orderBy(Select sel, ExpContext ctx, ExpState state,
670 boolean asc) {
671 sel.orderBy(getColumns(state), asc, sel.outer(state.joins), false);
672 }
673
674 public Object load(ExpContext ctx, ExpState state, Result res)
675 throws SQLException {
676 return load(ctx, state, res, false);
677 }
678
679 Object load(ExpContext ctx, ExpState state, Result res, boolean pks)
680 throws SQLException {
681 ClassMapping mapping = getClassMapping(state);
682 PathExpState pstate = (PathExpState) state;
683 if (mapping != null && (pstate.field == null
684 || !pstate.field.isEmbedded())) {
685 if (pks)
686 return mapping.getObjectId(ctx.store, res, null, true,
687 pstate.joins);
688 return res.load(mapping, ctx.store, ctx.fetch, pstate.joins);
689 }
690
691 Object ret;
692 if (_key)
693 ret = pstate.field.loadKeyProjection(ctx.store, ctx.fetch, res,
694 pstate.joins);
695 else
696 ret = pstate.field.loadProjection(ctx.store, ctx.fetch, res,
697 pstate.joins);
698 if (_cast != null)
699 ret = Filters.convert(ret, _cast);
700 return ret;
701 }
702
703 public void calculateValue(Select sel, ExpContext ctx, ExpState state,
704 Val other, ExpState otherState) {
705 // we don't create the SQL b/c it forces the Select to cache aliases
706 // for the tables we use, and these aliases might not ever be used if
707 // we eventually call appendIsEmpty or appendIsNull rather than appendTo
708 }
709
710 public int length(Select sel, ExpContext ctx, ExpState state) {
711 return getColumns(state).length;
712 }
713
714 public void appendTo(Select sel, ExpContext ctx, ExpState state,
715 SQLBuffer sql, int index) {
716 Column col = getColumns(state)[index];
717
718 // if select is null, it means we are not aliasing columns
719 // (e.g., during a bulk update)
720 if (sel == null)
721 sql.append(col.getName());
722 else if (_type == XPATH)
723 // if this is an xpath, append xpath string
724 sql.append(getXPath());
725 else
726 sql.append(sel.getColumnAlias(col, state.joins));
727 }
728
729 public void appendIsEmpty(Select sel, ExpContext ctx, ExpState state,
730 SQLBuffer sql) {
731 PathExpState pstate = (PathExpState) state;
732 if (pstate.field == null)
733 sql.append(FALSE);
734 else
735 pstate.field.appendIsEmpty(sql, sel, pstate.joins);
736 }
737
738 public void appendIsNotEmpty(Select sel, ExpContext ctx, ExpState state,
739 SQLBuffer sql) {
740 PathExpState pstate = (PathExpState) state;
741 if (pstate.field == null)
742 sql.append(FALSE);
743 else
744 pstate.field.appendIsNotEmpty(sql, sel, pstate.joins);
745 }
746
747 public void appendSize(Select sel, ExpContext ctx, ExpState state,
748 SQLBuffer sql) {
749 PathExpState pstate = (PathExpState) state;
750 if (pstate.field == null)
751 sql.append("1");
752 else
753 pstate.field.appendSize(sql, sel, pstate.joins);
754 }
755
756 public void appendIsNull(Select sel, ExpContext ctx, ExpState state,
757 SQLBuffer sql) {
758 PathExpState pstate = (PathExpState) state;
759 if (pstate.field == null)
760 sql.append(FALSE);
761 else
762 pstate.field.appendIsNull(sql, sel, pstate.joins);
763 }
764
765 public void appendIsNotNull(Select sel, ExpContext ctx, ExpState state,
766 SQLBuffer sql) {
767 PathExpState pstate = (PathExpState) state;
768 if (pstate.field == null)
769 sql.append(TRUE);
770 else
771 pstate.field.appendIsNotNull(sql, sel, pstate.joins);
772 }
773
774 public int hashCode() {
775 if (_actions == null)
776 return _candidate.hashCode();
777 return _candidate.hashCode() ^ _actions.hashCode();
778 }
779
780 public boolean equals(Object other) {
781 if (other == this)
782 return true;
783 if (!(other instanceof PCPath))
784 return false;
785 PCPath path = (PCPath) other;
786 return ObjectUtils.equals(_candidate, path._candidate)
787 && ObjectUtils.equals(_actions, path._actions);
788 }
789
790 /**
791 * Helper class representing an action.
792 */
793 private static class Action
794 implements Serializable {
795
796 public static final int GET = 0;
797 public static final int GET_OUTER = 1;
798 public static final int GET_KEY = 2;
799 public static final int VAR = 3;
800 public static final int SUBQUERY = 4;
801 public static final int UNBOUND_VAR = 5;
802 public static final int CAST = 6;
803 public static final int GET_XPATH = 7;
804
805 public int op = -1;
806 public Object data = null;
807
808 public String toString() {
809 return op + "|" + data;
810 }
811
812 public int hashCode() {
813 if (data == null)
814 return op;
815 return op ^ data.hashCode();
816 }
817
818 public boolean equals(Object other) {
819 if (other == this)
820 return true;
821 Action a = (Action) other;
822 return op == a.op
823 && ObjectUtils.equals(data, a.data);
824 }
825 }
826 }