001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.tests.shared;
034:
035: import com.flexive.shared.FxLanguage;
036: import com.flexive.shared.FxXMLUtils;
037: import com.flexive.shared.configuration.parameters.ObjectParameter;
038: import com.flexive.shared.exceptions.FxNotFoundException;
039: import com.flexive.shared.search.query.*;
040: import com.flexive.shared.structure.*;
041: import com.flexive.shared.tree.FxTreeMode;
042: import com.flexive.shared.value.FxBoolean;
043: import com.flexive.shared.value.FxNumber;
044: import com.flexive.shared.value.FxString;
045: import com.flexive.shared.value.FxValue;
046: import com.flexive.shared.value.mapper.InputMapper;
047: import org.apache.commons.lang.StringUtils;
048: import org.testng.annotations.DataProvider;
049: import org.testng.annotations.Test;
050:
051: import java.util.Arrays;
052: import java.util.List;
053:
054: /**
055: * Search query tree tests (GUI query editor).
056: *
057: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
058: */
059: public class QueryNodeTreeTests {
060: private static FxPropertyAssignment createAssignment() {
061: FxProperty property = new FxProperty(42, "testProperty",
062: new FxMultiplicity(0, 1), null, FxDataType.String1024);
063: return new FxPropertyAssignment(43, true, null, null, "test",
064: 0, property.getMultiplicity(), 0, null, 0, null, null,
065: null, property, null, FxLanguage.SYSTEM_ID, null);
066: }
067:
068: /**
069: * A primitive test node class.
070: */
071: public static class InnerTestNode extends
072: QueryValueNode<FxValue, PropertyValueComparator> {
073: private static final long serialVersionUID = -7628311116595464298L;
074:
075: public InnerTestNode() {
076: }
077:
078: public InnerTestNode(int id) {
079: this .id = id;
080: }
081:
082: public boolean isValid() {
083: return true;
084: }
085:
086: public void buildSqlQuery(SqlQueryBuilder builder) {
087: }
088:
089: public List<PropertyValueComparator> getNodeComparators() {
090: return Arrays.asList(PropertyValueComparator.values());
091: }
092: }
093:
094: /**
095: * Query node generator interface (used to customize tree-building
096: * routines).
097: */
098: public static interface QueryNodeGenerator<TNode extends QueryNode> {
099: TNode createNode(int nodeId);
100: }
101:
102: /**
103: * Internal node ID counter
104: */
105: private static int nodeId;
106:
107: @Test(groups={"shared"})
108: public void testNodeEquals() {
109: assert new InnerTestNode().equals(new InnerTestNode()) : "Two new node instances should be equal.";
110: //noinspection ObjectEqualsNull
111: assert !new InnerTestNode().equals(null) : "A test node is not equal to null.";
112: assert new InnerTestNode(10).equals(new InnerTestNode(10)) : "Node IDs not checked properly";
113: assert !new InnerTestNode(10).equals(new InnerTestNode(9)) : "Nodes with different IDs should not be equal.";
114: }
115:
116: /**
117: * Checks if the given node is stored correctly in an XML string (including
118: * its children) and throws a runtime exception if an error occured.
119: *
120: * @param root the root node of the tree to be checked
121: */
122: @Test(groups={"shared"},dataProvider="basicQueries")
123: public void checkTreeStore(QueryNode root) {
124: try {
125: QueryNode loadedRoot = (QueryNode) ObjectParameter
126: .getDefaultXStream().fromXML(
127: FxXMLUtils.toXML(ObjectParameter
128: .getDefaultXStream(), root));
129: assertEqualTrees(root, loadedRoot);
130: } catch (Exception e) {
131: throw new RuntimeException(e);
132: }
133: }
134:
135: /**
136: * Checks the tree validation with miscellaneous tree input.
137: */
138: @Test(groups={"shared"})
139: public void checkTreeValidity() {
140: assert buildFlatTree(
141: new AssignmentNodeGenerator(FxDataType.String1024,
142: new FxString("Test")), 5).isValid() : "Simple string tree should be valid";
143: StringBuilder bigString = new StringBuilder();
144: for (int i = 0; i < 1000; i++) {
145: bigString.append("1234567890");
146: }
147: assert bigString.length() == 10000 : "String length to be expected 10000";
148: assert !buildFlatTree(
149: new AssignmentNodeGenerator(FxDataType.String1024,
150: new FxString(bigString.substring(0, 1100))), 5)
151: .isValid() : "Tree with 1100 character strings shouldn't be valid";
152: // assert !buildNestedTree(
153: // 25,
154: // new AssignmentNodeGenerator(FxDataType.String1024, bigString
155: // .substring(0, 256))).isValid() : "256 character string should'nt be valid";
156: assert buildNestedTree(
157: 25,
158: new AssignmentNodeGenerator(FxDataType.String1024,
159: new FxString(bigString.substring(0, 255))), 5)
160: .isValid() : "255 character string should be valid";
161: assert !buildFlatTree(
162: new AssignmentNodeGenerator(FxDataType.String1024, null),
163: 5).isValid() : "Null string tree should'nt be valid.";
164:
165: // TODO check other data types
166: }
167:
168: /**
169: * Checks the tree node removal
170: */
171: @Test(groups={"shared"})
172: public void treeNodeRemoval() {
173: QueryNode flatTree = buildFlatTree(new InnerNodeGenerator(), 5);
174: int children = flatTree.getChildren().size();
175: // remove first child
176: QueryNode child = flatTree.getChildren().get(0);
177: assert flatTree.removeChild(child).equals(flatTree) : "Incorrent parent node returned";
178: assert flatTree.getChildren().indexOf(child) == -1 : "Child not removed";
179: assert flatTree.getChildren().size() == children - 1 : "Incorrect number of children";
180:
181: // remove last child
182: child = flatTree.getChildren().get(
183: flatTree.getChildren().size() - 1);
184: assert flatTree.removeChild(child).equals(flatTree) : "Incorrent parent node returned";
185: assert flatTree.getChildren().indexOf(child) == -1 : "Child not removed";
186: assert flatTree.getChildren().size() == children - 2 : "Incorrent number of children";
187:
188: while (flatTree.getChildren().size() > 0) {
189: assert flatTree.removeChild(flatTree.getChildren().get(0))
190: .equals(flatTree) : "Incorrent parent node";
191: }
192:
193: // test nested tree
194: QueryNode nestedTree = buildNestedTree(5,
195: new InnerNodeGenerator(), 5);
196: for (QueryNode node : nestedTree.getChildren()) {
197: if (node instanceof QueryOperatorNode) {
198: assert nestedTree
199: .removeChild(node.getChildren().get(0)).equals(
200: node) : "Returned incorrent parent node in nested tree.";
201: }
202: }
203:
204: // test automatic removal of empty inner operator nodes
205: QueryNode innerTree = buildNestedTree(5,
206: new InnerNodeGenerator(), 5);
207: QueryNode emptyNode = innerTree.getChild(innerTree
208: .getChildren().size() - 1);
209: assert emptyNode instanceof QueryOperatorNode : "Unexpected node type: "
210: + emptyNode.getClass();
211: assert innerTree.getChildren().indexOf(emptyNode) != -1 : "Test node not found";
212: while (emptyNode.getChildren().size() > 0) {
213: QueryNode node = emptyNode.getChild(0);
214: emptyNode.removeChild(node);
215: }
216: assert innerTree.getChildren().indexOf(emptyNode) == -1 : "Empty test node not removed.";
217: }
218:
219: @Test(groups="shared")
220: public void emptyOuterNodeRemoval() {
221: // test automatic removal of empty outer operator nodes
222: // (i.e. operator nodes that contain only a single operator node)
223: QueryRootNode outerTree = buildNestedTree(5,
224: new InnerNodeGenerator(), 5);
225: QueryNode newRootNode = outerTree.getChild(outerTree
226: .getChildren().size() - 1);
227: assert newRootNode instanceof QueryOperatorNode : "Unexpected node type: "
228: + newRootNode.getClass();
229: for (int i = 0; i < 5; i++) {
230: outerTree.removeChild(outerTree.getChild(0));
231: }
232: // now the children of newRootNode should be attached to the root node
233: assert outerTree.getChildren().size() == 6 : "Unexpected number of children: "
234: + outerTree.getChildren().size()
235: + ",\n Tree="
236: + outerTree.toString();
237: }
238:
239: @Test(groups="shared")
240: public void singleChildRemoval() {
241: // test automatic removal of inner operator nodes with only one child
242: QueryRootNode nestedTree2 = buildNestedTree(1,
243: new InnerNodeGenerator(), 5);
244: QueryNode operatorNode1 = nestedTree2.getChild(nestedTree2
245: .getChildren().size() - 1);
246: for (int i = 0; i < 3; i++) {
247: operatorNode1.removeChild(operatorNode1.getChild(0));
248: }
249: assert operatorNode1.getChildren().size() == 2 : "Expected 2 children, got: "
250: + operatorNode1.getChildren().size();
251: assert nestedTree2.getChildren().size() == 6 : "Expected 6 children, got: "
252: + nestedTree2.getChildren().size();
253: QueryNode lastChild = operatorNode1.getChild(1);
254: operatorNode1.removeChild(operatorNode1.getChild(0));
255: assert lastChild.equals(nestedTree2.getChildren().get(
256: nestedTree2.getChildren().size() - 1)) : "Tree not compacted properly: "
257: + nestedTree2.toString();
258: }
259:
260: /**
261: * Checks searching inside query trees
262: *
263: * @throws FxNotFoundException on errors
264: */
265: @Test(groups={"shared"})
266: public void treeNodeFind() throws FxNotFoundException {
267: QueryNode nestedTree = buildNestedTree(5,
268: new InnerNodeGenerator(), 5);
269: // check basic queries
270: assert nestedTree.findChild(nestedTree.getChild(0).getId()) != null;
271: assert nestedTree.findChild(nestedTree.getChild(
272: nestedTree.getChildren().size() - 1).getId()) != null;
273: assert nestedTree.findChild(nestedTree.getId()) != null;
274:
275: // check nested find queries
276: QueryNode innerNode = nestedTree.getChild(nestedTree
277: .getChildren().size() - 1);
278: assert nestedTree.findChild(innerNode.getChild(0).getId()) != null;
279:
280: // check exception
281: try {
282: nestedTree.findChild(-1);
283: assert false : "Invalid node found!";
284: } catch (Exception e) {
285: // pass
286: }
287: try {
288: nestedTree.getChild(0).findChild(
289: nestedTree.getChild(0).getId());
290: assert false : "Searching in leaf nodes should not be allowed";
291: } catch (Exception e) {
292: // pass
293: }
294:
295: }
296:
297: @Test(groups="shared")
298: public void simpleNodeVisitor() {
299: CountingNodeVisitor visitor = new CountingNodeVisitor();
300: buildFlatTree(new InnerNodeGenerator(), 25).visit(visitor);
301: assert visitor.getOpNodes() == 1 : "Unexpected number of operator nodes: "
302: + visitor.getOpNodes();
303: assert visitor.getValueNodes() == 25 : "Unexpected number of value nodes: "
304: + visitor.getValueNodes();
305:
306: visitor = new CountingNodeVisitor();
307: buildNestedTree(50, new InnerNodeGenerator(), 10)
308: .visit(visitor);
309: assert visitor.getOpNodes() == 51 : "Unexpected number of operator nodes: "
310: + visitor.getOpNodes();
311: assert visitor.getValueNodes() == 51 * 10 : "Unexpected number of value nodes: "
312: + visitor.getValueNodes();
313: }
314:
315: @Test(groups="shared")
316: public void maxIdVisitor() {
317: MaxNodeIdVisitor maxIdVisitor = new MaxNodeIdVisitor();
318: nodeId = 0;
319: buildFlatTree(new InnerNodeGenerator(), 25).visit(maxIdVisitor);
320: assert maxIdVisitor.getMaxId() == 25 : "Unexpected max ID: "
321: + maxIdVisitor.getMaxId();
322:
323: maxIdVisitor = new MaxNodeIdVisitor();
324: nodeId = 0;
325: buildNestedTree(10, new InnerNodeGenerator(), 10).visit(
326: maxIdVisitor);
327: assert maxIdVisitor.getMaxId() == 11 * 11 - 1 : "Unexpected max ID: "
328: + maxIdVisitor.getMaxId();
329: }
330:
331: /**
332: * Check if multiple calls to {@link com.flexive.shared.search.query.QueryOperatorNode#getSqlQuery()}
333: * yield the same result.
334: */
335: @Test(groups="shared")
336: public void repeatableSqlQueryTest() {
337: final QueryRootNode root = buildNestedTree(5,
338: new AssignmentNodeGenerator(FxDataType.String1024,
339: new FxString("test value")), 5);
340: final String query = root.getSqlQuery();
341: final String query2 = root.getSqlQuery();
342: assert query.equals(query2) : "Second call to getSqlQuery() returned different result than first one : "
343: + "\n[1]: " + query + "\n[2]: " + query2;
344: }
345:
346: /**
347: * Check if multiple calls to {@link com.flexive.shared.search.query.QueryOperatorNode#getSqlQuery()}
348: * yield the same result, this time with an external query builder.
349: */
350: @Test(groups="shared")
351: public void repeatableSqlQueryTest2() {
352: SqlQueryBuilder builder = new SqlQueryBuilder();
353: final QueryRootNode root = buildNestedTree(5,
354: new AssignmentNodeGenerator(FxDataType.String1024,
355: new FxString("test value")), 5);
356: root.buildSqlQuery(builder);
357: final String query = builder.getQuery();
358: root.buildSqlQuery(builder);
359: final String query2 = builder.getQuery();
360: assert StringUtils.isNotBlank(query) && query.equals(query2) : "Second call to getQuery() returned different result than first one : "
361: + "\n[1]: " + query + "\n[2]: " + query2;
362: }
363:
364: /**
365: * Checks against a bug where an empty tree leads to the generation
366: * of "WHERE ()" in the SQL query.
367: */
368: @Test(groups="shared")
369: public void emptyQueryTest() {
370: final SqlQueryBuilder builder = new SqlQueryBuilder();
371: final QueryRootNode root = new QueryRootNode(
372: QueryRootNode.Type.CONTENTSEARCH);
373: root.buildSqlQuery(builder);
374: assert !builder.getQuery().contains("FROM WHERE") : "Query contains invalid FROM clause: "
375: + builder.getQuery();
376: assert !builder.getQuery().contains("WHERE ()") : "Query contains invalid WHERE clause: "
377: + builder.getQuery();
378: }
379:
380: /**
381: * Testcase for a bug that duplicates tree nodes
382: * for a certain order of commands (cause: invalid parent nodes).
383: */
384: @Test(groups="shared")
385: public void treeDeleteCloneBug() {
386: QueryRootNode root = new QueryRootNode(
387: QueryRootNode.Type.CONTENTSEARCH);
388: root.addChild(new InnerTestNode(nodeId++));
389: root.addChild(new InnerTestNode(nodeId++));
390: combineNodes(root, root.getChild(1),
391: new InnerTestNode(nodeId++));
392: root.getChild(1).addChild(new InnerTestNode(nodeId++));
393: combineNodes(root.getChild(1), root.getChild(1).getChild(1),
394: new InnerTestNode(nodeId++));
395:
396: removeFromParent(root.getChild(1).getChild(1));
397: removeFromParent(root.getChild(1).getChild(0));
398: removeFromParent(root.getChild(1).getChild(0));
399: assert root.getChildren().size() == 2 : "Expected 2 children, got: "
400: + root.getChildren().size();
401: }
402:
403: /**
404: * Simple tests for the tree node level method.
405: */
406: @Test(groups="shared")
407: public static void nodeLevelTest() {
408: QueryRootNode root = buildNestedTree(5,
409: new InnerNodeGenerator(), 1);
410: assert root.getLevel() == 0 : "Root level should be 0, got: "
411: + root.getLevel();
412: assert root.getChild(0).getLevel() == 1 : "First child level should be 1, got: "
413: + root.getChild(0).getLevel();
414: assert root.getChild(1).getLevel() == 1;
415: assert root.getChild(1).getChild(0).getLevel() == 2;
416: assert root.getChild(1).getChild(1).getLevel() == 2;
417: assert root.getChild(1).getChild(1).getChild(0).getLevel() == 3;
418: }
419:
420: @Test(groups="shared")
421: public void joinNodesFlat() {
422: QueryRootNode root = buildNestedTree(0,
423: new InnerNodeGenerator(), 3);
424: QueryOperatorNode operatorNode = root.joinNodes(Arrays.asList(
425: root.getChild(0).getId(), root.getChild(1).getId()),
426: QueryOperatorNode.Operator.OR);
427: assert operatorNode.getChildren().size() == 2 : "New operator node must have 2 children, got: "
428: + operatorNode.getChildren().size();
429: assert operatorNode.getParent() == root : "Operator node should be attached to root.";
430: assert root.getChildren().size() == 2 : "Root node should have two children, got: "
431: + root.getChildren().size();
432: }
433:
434: @Test(groups="shared")
435: public void joinNodesNested() {
436: QueryRootNode root = buildNestedTree(1,
437: new InnerNodeGenerator(), 4);
438: assert root.getChildren().size() == 5 : "Expected 5 root children, got: "
439: + root.getChildren().size();
440: assert root.getChild(4).getChildren().size() == 4 : "Expected 4 children, got: "
441: + root.getChild(4).getChildren().size();
442: QueryOperatorNode operatorNode = root.joinNodes(Arrays.asList(
443: root.getChild(4).getChild(0).getId(), root.getChild(4)
444: .getChild(2).getId()),
445: QueryOperatorNode.Operator.OR);
446: assert root.getChildren().size() == 5 : "Expected 5 root children, got: "
447: + root.getChildren().size();
448: assert root.getChild(4).getChildren().size() == 3 : "Expected 2 children, got: "
449: + root.getChild(4).getChildren().size();
450: assert operatorNode.getChildren().size() == 2;
451: assert operatorNode.getParent() == root.getChild(4);
452: }
453:
454: @Test(groups="shared")
455: public void joinNodesNestedCompact() {
456: QueryRootNode root = buildNestedTree(1,
457: new InnerNodeGenerator(), 3);
458: assert root.getChildren().size() == 4 : "Expected 4 root children, got: "
459: + root.getChildren().size();
460: assert root.getChild(3).getChildren().size() == 3 : "Expected 3 children, got: "
461: + root.getChild(3).getChildren().size();
462: // join 2 of the 3 childs in root child #3, then the remaining node should be reattached to the root node itself
463: QueryNode compactedChild = root.getChild(3).getChild(2); // this node should be reattached to root automatically
464: assert compactedChild.getParent() != root;
465: final QueryNode joinNode = root.getChild(3);
466: QueryOperatorNode operatorNode = root.joinNodes(Arrays.asList(
467: joinNode.getChild(0).getId(), joinNode.getChild(1)
468: .getId(), joinNode.getParent().getChild(0)
469: .getId()), QueryOperatorNode.Operator.OR);
470: assert compactedChild.getParent() == root : "Single child should have been reattached to root";
471: assert root.getChildren().size() == 4 : "Expected 4 root children, got: "
472: + root.getChildren().size();
473: assert operatorNode.getChildren().size() == 3;
474: assert operatorNode.getParent() == root;
475: }
476:
477: @Test(groups="shared")
478: public void joinNodesNested2() {
479: QueryRootNode root = buildNestedTree(2,
480: new InnerNodeGenerator(), 3);
481: assert root.getChildren().size() == 4;
482: assert root.getChild(3).getChildren().size() == 4;
483: assert root.getChild(3).getChild(3).getChildren().size() == 3;
484: // combine two nodes in the third level - should attach as a child to the third level root, not the second level one
485: final QueryNode joinRoot = root.getChild(3).getChild(3);
486: final QueryOperatorNode operatorNode = root.joinNodes(Arrays
487: .asList(joinRoot.getChild(0).getId(), joinRoot
488: .getChild(1).getId()),
489: QueryOperatorNode.Operator.OR);
490: assert joinRoot.getChildren().size() == 2 : "Joined node should attach to previous parent";
491: assert joinRoot.getParent().getChildren().size() == 4;
492: }
493:
494: @Test(groups="shared")
495: public void joinNodesLevelAll() {
496: QueryRootNode root = buildNestedTree(0,
497: new InnerNodeGenerator(), 3);
498: assert root.getChildren().size() == 3;
499: QueryOperatorNode operatorNode = root.joinNodes(Arrays.asList(
500: root.getChild(0).getId(), root.getChild(1).getId(),
501: root.getChild(2).getId()),
502: QueryOperatorNode.Operator.OR);
503: assert operatorNode == root : "Nodes should have been attached to the root node.";
504: assert operatorNode.getChildren().size() == 3 : "All 3 nodes should be attached to the operator node";
505: assert operatorNode.getOperator().equals(
506: QueryOperatorNode.Operator.OR);
507: }
508:
509: @Test(groups="shared")
510: public void joinNodesFromDistinctGroups() {
511: QueryRootNode root = buildNestedTree(0,
512: new InnerNodeGenerator(), 4);
513: // create two subqueries
514: final QueryNode child0_0 = root.getChild(0);
515: final QueryNode child0_1 = root.getChild(1);
516: root.joinNodes(Arrays
517: .asList(child0_0.getId(), child0_1.getId()),
518: QueryOperatorNode.Operator.OR);
519: final QueryNode child1_0 = root.getChild(0);
520: final QueryNode child1_1 = root.getChild(1);
521: root.joinNodes(Arrays
522: .asList(child1_0.getId(), child1_1.getId()),
523: QueryOperatorNode.Operator.OR);
524: assert root.getChildren().size() == 2;
525: final QueryNode sub0 = root.getChild(0); // subquery 0
526: final QueryNode sub1 = root.getChild(1); // subquery 1
527: assert sub0.getChildren().containsAll(
528: Arrays.asList(child0_0, child0_1));
529: assert sub1.getChildren().containsAll(
530: Arrays.asList(child1_0, child1_1));
531:
532: // join second node from sub0 with first node from sub1
533: QueryOperatorNode joinRoot = root.joinNodes(Arrays.asList(
534: child0_1.getId(), child1_0.getId()),
535: QueryOperatorNode.Operator.AND);
536: // expected result: [child0_0, child1_1, AND[child_0_1, child1_0]]
537: assert root.getChildren().size() == 3 : "Root should have three children now.";
538: assert joinRoot.getParent() == root : "Join root should have been attached to root";
539: assert root.getChild(1).isValueNode() : "Node 1 should be value node";
540: assert root.getChild(2).isValueNode() : "Node 2 should be value node";
541: assert !root.getChild(0).isValueNode() : "Node 0 should not be value node";
542: }
543:
544: // tests against a bug where a joined group is duplicated in itself
545: // (caused by aliasing issues when moving the tree nodes in the same subtree)
546: @Test(groups="shared")
547: public void joinNodesFromDistinctGroupsCopyBug() {
548: QueryRootNode root = buildNestedTree(0,
549: new InnerNodeGenerator(), 4);
550: // create two subqueries
551: final QueryNode child0_0 = root.getChild(0);
552: final QueryNode child0_1 = root.getChild(1);
553: root.joinNodes(Arrays
554: .asList(child0_0.getId(), child0_1.getId()),
555: QueryOperatorNode.Operator.OR);
556: final QueryNode child1_0 = root.getChild(0);
557: final QueryNode child1_1 = root.getChild(1);
558: root.joinNodes(Arrays
559: .asList(child1_0.getId(), child1_1.getId()),
560: QueryOperatorNode.Operator.OR);
561: assert root.getChildren().size() == 2;
562: final QueryNode sub0 = root.getChild(0); // subquery 0
563: final QueryNode sub1 = root.getChild(1); // subquery 1
564: assert sub0.getChildren().containsAll(
565: Arrays.asList(child0_0, child0_1));
566: assert sub1.getChildren().containsAll(
567: Arrays.asList(child1_0, child1_1));
568:
569: // join both nodes from sub1 again
570: QueryOperatorNode joinRoot = root.joinNodes(Arrays.asList(
571: child1_0.getId(), child1_1.getId()),
572: QueryOperatorNode.Operator.AND);
573: assert joinRoot.getParent() == root : "Joined nodes should have been attached to the root node";
574: }
575:
576: @Test(groups="shared")
577: public void treeNodeTest() {
578: QueryRootNode root = new QueryRootNode(0,
579: QueryRootNode.Type.CONTENTSEARCH);
580: final TreeValueNode child1 = new TreeValueNode(root.getNewId(),
581: 1, FxTreeMode.Edit, new FxString(false, "node 1"));
582: child1
583: .setComparator(TreeValueNode.TreeValueComparator.DIRECTCHILD);
584: root.addChild(child1);
585: final TreeValueNode child2 = new TreeValueNode(root.getNewId(),
586: 2, FxTreeMode.Edit, new FxString(false, "node 2"));
587: child2.setComparator(TreeValueNode.TreeValueComparator.CHILD);
588: root.addChild(child2);
589: assert root.isValid();
590: assert child1.getAvailableComparators().containsAll(
591: Arrays.asList(TreeValueNode.TreeValueComparator
592: .values()));
593: assert root.getSqlQuery().contains(
594: "IS DIRECT CHILD OF 1 AND IS CHILD OF 2");
595: }
596:
597: @Test(groups="shared")
598: public void selectListNodeTest() {
599: FxSelectList list = new FxSelectList("test");
600: final FxSelectListItem item1 = new FxSelectListItem(1, list,
601: -1, new FxString("item 1"));
602: final FxSelectListItem item2 = new FxSelectListItem(2, list,
603: -1, new FxString("item 2"));
604: QueryRootNode root = new QueryRootNode(0,
605: QueryRootNode.Type.CONTENTSEARCH);
606: final FxPropertyAssignment assignment = createAssignment();
607: final SelectValueNode child1 = new SelectValueNode(root
608: .getNewId(), assignment, item1);
609: child1.setComparator(PropertyValueComparator.EQ);
610: root.addChild(child1);
611: final SelectValueNode child2 = new SelectValueNode(root
612: .getNewId(), assignment, item2);
613: child2.setComparator(PropertyValueComparator.NE);
614: root.addChild(child2);
615: assert root.isValid();
616: assert root.getSqlQuery().contains(
617: "co.#" + assignment.getId() + " = " + item1.getId());
618: assert root.getSqlQuery().contains(
619: "co.#" + assignment.getId() + " != " + item2.getId());
620: }
621:
622: @Test(groups="shared")
623: public void propertyValueNodeTest() {
624: QueryRootNode root = new QueryRootNode(0,
625: QueryRootNode.Type.CONTENTSEARCH);
626: final FxProperty property = createAssignment().getProperty();
627: final PropertyValueNode child1 = new PropertyValueNode(root
628: .getNewId(), property);
629: child1.setComparator(PropertyValueComparator.EQ);
630: child1.setValue(new FxNumber(1));
631: root.addChild(child1);
632: final PropertyValueNode child2 = new PropertyValueNode(root
633: .getNewId(), property);
634: child2.setComparator(PropertyValueComparator.NE);
635: child2.setValue(new FxNumber(2));
636: root.addChild(child2);
637: assert root.isValid();
638: assert root.getSqlQuery().contains(
639: "co." + property.getName() + " = 1 AND co."
640: + property.getName() + " != 2");
641: }
642:
643: @Test(groups="shared")
644: public void inputMapperComparatorTest() {
645: final List<PropertyValueComparator> allowedComparators = Arrays
646: .asList(PropertyValueComparator.EMPTY,
647: PropertyValueComparator.EQ);
648: final InnerTestNode node = new InnerTestNode(1);
649: assert node.getAvailableComparators().equals(
650: Arrays.asList(PropertyValueComparator.values()));
651: node.setInputMapper(new InputMapper<FxString, FxBoolean>() {
652: @Override
653: public FxBoolean encode(FxString value) {
654: return new FxBoolean(Boolean.valueOf(value
655: .getDefaultTranslation()));
656: }
657:
658: @Override
659: public List<? extends ValueComparator> getAvailableValueComparators() {
660: return allowedComparators;
661: }
662: });
663: assert node.getAvailableComparators().containsAll(
664: allowedComparators)
665: && allowedComparators.size() == node
666: .getAvailableComparators().size() : "Comparators should be "
667: + allowedComparators
668: + ", is: "
669: + node.getAvailableComparators();
670: }
671:
672: private void removeFromParent(QueryNode node) {
673: node.getParent().removeChild(node);
674: }
675:
676: private void combineNodes(QueryNode parent, QueryNode targetNode,
677: QueryNode newNode) {
678: QueryOperatorNode operatorNode = new QueryOperatorNode(nodeId++);
679: parent.addChild(operatorNode);
680: operatorNode.addChild(targetNode);
681: operatorNode.addChild(newNode);
682: parent.removeChild(targetNode); // remove node that's now nested in level 1
683: }
684:
685: /**
686: * Asserts that both tree are equal.
687: *
688: * @param root1 root node of the first tree
689: * @param root2 root node of the second tree
690: */
691: public static void assertEqualTrees(QueryNode root1, QueryNode root2) {
692: if ((root1 == null && root2 != null) || !root1.equals(root2)) {
693: assert false : "Trees are not equal: " + root1 + ", "
694: + root2;
695: }
696: if (root1.getChildren().size() == root2.getChildren().size()) {
697: for (int i = 0; i < root1.getChildren().size(); i++) {
698: assertEqualTrees(root1.getChildren().get(i), root2
699: .getChildren().get(i));
700: }
701: } else {
702: assert false : "Tree children are different: "
703: + root1.getChildren().size() + " != "
704: + root2.getChildren().size();
705: }
706: }
707:
708: /**
709: * Provides a basic set of "test" query trees.
710: *
711: * @return a basic set of "test" query trees.
712: */
713: @DataProvider(name="basicQueries")
714: public Object[][] getBasicQueries() {
715: nodeId = 0; // reset global ID counter
716: return new Object[][] {
717: { buildSimpleNode() },
718: { buildFlatTree(new InnerNodeGenerator(), 5) },
719: { buildNestedTree(5, new InnerNodeGenerator(), 5) },
720: { buildNestedTree(25, new InnerNodeGenerator(), 5) },
721:
722: { buildFlatTree(new AssignmentNodeGenerator(
723: FxDataType.String1024, new FxString("Test")), 5) },
724: { buildNestedTree(5, new AssignmentNodeGenerator(
725: FxDataType.String1024, new FxString("Test")), 5) },
726: { buildNestedTree(25, new AssignmentNodeGenerator(
727: FxDataType.String1024, new FxString("Test")), 5) } };
728: }
729:
730: /**
731: * Creates a single test node
732: *
733: * @return node
734: */
735: public static QueryNode buildSimpleNode() {
736: QueryNode node = new InnerTestNode();
737: node.setId(42);
738: node.setParent(null);
739: return node;
740: }
741:
742: /**
743: * Build a flat tree containing some nodes under a root node.
744: *
745: * @param numNodes TODO
746: */
747: public static QueryRootNode buildFlatTree(
748: QueryNodeGenerator generator, int numNodes) {
749: QueryRootNode root = new QueryRootNode(nodeId++,
750: QueryRootNode.Type.CONTENTSEARCH);
751: for (int i = 0; i < numNodes; i++) {
752: root.addChild(generator.createNode(nodeId++));
753: }
754: return root;
755: }
756:
757: /**
758: * Build a nested tree structure with up to maxLevel levels.
759: *
760: * @param maxLevel the maximum tree depth
761: * @param generator the node generator(?)
762: * @param numNodesPerLevel TODO
763: * @return a nested tree structure with up to maxLevel levels.
764: */
765: public static QueryRootNode buildNestedTree(int maxLevel,
766: QueryNodeGenerator generator, int numNodesPerLevel) {
767: QueryRootNode root = buildFlatTree(generator, numNodesPerLevel);
768: for (int level = 0; level < maxLevel; level++) {
769: root.setParent(buildFlatTree(generator, numNodesPerLevel));
770: root = (QueryRootNode) root.getParent();
771: }
772: return root;
773: }
774:
775: /**
776: * Generates InnerTestNode query nodes.
777: */
778: public static class InnerNodeGenerator implements
779: QueryNodeGenerator<InnerTestNode> {
780: public InnerTestNode createNode(int nodeId) {
781: return new InnerTestNode(nodeId);
782: }
783: }
784:
785: public static class AssignmentNodeGenerator implements
786: QueryNodeGenerator<AssignmentValueNode> {
787: private final FxPropertyAssignment assignment;
788:
789: private final FxValue value;
790:
791: public AssignmentNodeGenerator(FxDataType dataType,
792: FxValue value) {
793: this .assignment = createAssignment();
794: this .value = value;
795: }
796:
797: public AssignmentValueNode createNode(int nodeId) {
798: AssignmentValueNode node = new AssignmentValueNode(nodeId,
799: assignment);
800: node.setValue(value != null ? value.copy() : null);
801: return node;
802: }
803:
804: public FxPropertyAssignment getAssignment() {
805: return assignment;
806: }
807: }
808:
809: private static class CountingNodeVisitor implements
810: QueryNodeVisitor {
811: private int opNodes = 0;
812: private int valueNodes = 0;
813:
814: public void visit(QueryOperatorNode operatorNode) {
815: opNodes++;
816: }
817:
818: public void visit(QueryValueNode valueNode) {
819: valueNodes++;
820: }
821:
822: public void setCurrentParent(QueryOperatorNode operatorNode) {
823: // ignore
824: }
825:
826: public int getOpNodes() {
827: return opNodes;
828: }
829:
830: public int getValueNodes() {
831: return valueNodes;
832: }
833:
834: }
835: }
|