001: /*
002: The contents of this file are subject to the Common Public Attribution License
003: Version 1.0 (the "License"); you may not use this file except in compliance with
004: the License. You may obtain a copy of the License at
005: http://www.projity.com/license . The License is based on the Mozilla Public
006: License Version 1.1 but Sections 14 and 15 have been added to cover use of
007: software over a computer network and provide for limited attribution for the
008: Original Developer. In addition, Exhibit A has been modified to be consistent
009: with Exhibit B.
010:
011: Software distributed under the License is distributed on an "AS IS" basis,
012: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
013: specific language governing rights and limitations under the License. The
014: Original Code is OpenProj. The Original Developer is the Initial Developer and
015: is Projity, Inc. All portions of the code written by Projity are Copyright (c)
016: 2006, 2007. All Rights Reserved. Contributors Projity, Inc.
017:
018: Alternatively, the contents of this file may be used under the terms of the
019: Projity End-User License Agreeement (the Projity License), in which case the
020: provisions of the Projity License are applicable instead of those above. If you
021: wish to allow use of your version of this file only under the terms of the
022: Projity License and not to allow others to use your version of this file under
023: the CPAL, indicate your decision by deleting the provisions above and replace
024: them with the notice and other provisions required by the Projity License. If
025: you do not delete the provisions above, a recipient may use your version of this
026: file under either the CPAL or the Projity License.
027:
028: [NOTE: The text of this license may differ slightly from the text of the notices
029: in Exhibits A and B of the license at http://www.projity.com/license. You should
030: use the latest text at http://www.projity.com/license for your modifications.
031: You may not remove this license text from the source files.]
032:
033: Attribution Information: Attribution Copyright Notice: Copyright © 2006, 2007
034: Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj,
035: an open source solution from Projity. Attribution URL: http://www.projity.com
036: Graphic Image as provided in the Covered Code as file: openproj_logo.png with
037: alternatives listed on http://www.projity.com/logo
038:
039: Display of Attribution Information is required in Larger Works which are defined
040: in the CPAL as a work which combines Covered Code or portions thereof with code
041: not governed by the terms of the CPAL. However, in addition to the other notice
042: obligations, all copies of the Covered Code in Executable and Source Code form
043: distributed must, as a form of attribution of the original author, include on
044: each user interface screen the "OpenProj" logo visible to all users. The
045: OpenProj logo should be located horizontally aligned with the menu bar and left
046: justified on the top left of the screen adjacent to the File menu. The logo
047: must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it
048: must direct them back to http://www.projity.com.
049: */
050: package com.projity.pm.graphic.model.transform;
051:
052: import java.util.ArrayList;
053: import java.util.Comparator;
054: import java.util.Enumeration;
055: import java.util.HashMap;
056: import java.util.Iterator;
057: import java.util.LinkedList;
058: import java.util.List;
059: import java.util.ListIterator;
060: import java.util.Map;
061: import java.util.Stack;
062:
063: import org.apache.commons.collections.Closure;
064: import org.apache.commons.collections.MultiHashMap;
065: import org.apache.commons.collections.MultiMap;
066:
067: import com.projity.document.Document;
068: import com.projity.grouping.core.GroupNodeImpl;
069: import com.projity.grouping.core.Node;
070: import com.projity.grouping.core.NodeFactory;
071: import com.projity.grouping.core.model.WalkersNodeModel;
072: import com.projity.grouping.core.transform.HierarchicObject;
073: import com.projity.grouping.core.transform.ViewConfiguration;
074: import com.projity.grouping.core.transform.ViewTransformer;
075: import com.projity.grouping.core.transform.filtering.BaseFilter;
076: import com.projity.grouping.core.transform.filtering.NodeFilter;
077: import com.projity.grouping.core.transform.grouping.NodeGroup;
078: import com.projity.grouping.core.transform.grouping.NodeGrouper;
079: import com.projity.grouping.core.transform.sorting.NodeSorter;
080: import com.projity.grouping.core.transform.transformer.NodeTransformer;
081: import com.projity.pm.graphic.model.cache.GraphicNode;
082: import com.projity.pm.graphic.model.cache.ReferenceNodeModelCache;
083:
084: /**
085: *
086: */
087: public class NodeCacheTransformer implements CacheTransformer {
088: protected ViewTransformer transformer;
089:
090: protected ReferenceNodeModelCache refCache;
091: protected int levelOffset = 0;
092: protected String viewName;
093: protected ViewConfiguration view;
094:
095: public NodeCacheTransformer(String viewName,
096: ReferenceNodeModelCache refCache, Closure transformerClosure) {
097: //System.out.println("viewName="+viewName);
098: view = ViewConfiguration.getView(viewName);
099: transformer = view.getTransform();
100: if (transformerClosure != null)
101: transformerClosure.execute(transformer);
102:
103: this .refCache = refCache;
104: this .viewName = viewName;
105: }
106:
107: public ViewTransformer getTransformer() {
108: return transformer;
109: }
110:
111: public void transfrom(List list) {
112: model.clear();
113:
114: if (list == null)
115: return;
116:
117: boolean preserveHierarchy = transformer.isPreserveHierarchy();
118:
119: if (!transformer.isShowSummary()) {
120: preserveHierarchy = false;
121: removeSummaries(list);
122: }
123: Map<GraphicNode, List<GraphicNode>> assignmentsMap = null;
124: if (!transformer.isShowAssignments())
125: removeAssignments(list);
126: if (!transformer.isShowEmptyLines())
127: removeVoids(list);
128: if (transformer.isShowEmptyLines()
129: && !transformer.isShowEndEmptyLines())
130: removeEndVoids(list);
131:
132: NodeTransformer composition = transformer.getTransformer();
133:
134: NodeFilter hiddenFilter = transformer.getHiddenFilter();
135: if (hiddenFilter instanceof BaseFilter
136: && !((BaseFilter) hiddenFilter).isActive())
137: hiddenFilter = null; //to avoid useless filtering in case of BaseFilter
138: NodeFilter userFilter = (transformer.isNoneFilter()) ? null
139: : transformer.getUserFilter();
140: boolean filtering = hiddenFilter != null || userFilter != null;
141:
142: NodeSorter sorter1 = transformer.getHiddenSorter();
143: NodeSorter sorter2 = transformer.getUserSorter();
144: boolean sorting = !(sorter1 == null && transformer
145: .isNoneSorter());
146:
147: NodeGrouper grouper = transformer.getUserGrouper();
148: boolean grouping = !transformer.isNoneGrouper();
149:
150: if (!filtering && !sorting && !grouping)
151: return;
152:
153: if (transformer.isShowAssignments() && preserveHierarchy
154: && !transformer.isTreatAssignmentsAsTasks())
155: assignmentsMap = extractAssignments(list);
156:
157: List localList = null;
158: Stack parents = null;
159: if (preserveHierarchy) {
160: localList = new ArrayList();
161: parents = new Stack();
162: } else
163: localList = list;
164:
165: GraphicNode gnode, previous = null;
166: Object current;
167: for (Iterator i = list.iterator(); i.hasNext();) {
168: gnode = (GraphicNode) i.next();
169: if (!gnode.isVoid()) {
170: current = (composition == null) ? gnode.getNode()
171: : composition.evaluate(gnode.getNode());
172:
173: if (hiddenFilter != null) {
174: if (!hiddenFilter.evaluate(current)) {
175: if (!gnode.isSummary() || !preserveHierarchy) {
176: i.remove();
177: continue;
178: }
179: }
180: }
181: if (userFilter != null) {
182: if (!userFilter.evaluate(current)) {
183: if (!gnode.isSummary() || !preserveHierarchy) {
184: i.remove();
185: continue;
186: }
187: }
188: }
189: }
190: if (preserveHierarchy) {
191: //contruct a temporary tree for sorting and grouping
192: // if (parents==null||previous==null){
193: // System.out.println("null");
194: // }
195: if (gnode.getLevel() == 1) {
196: localList.add(gnode);
197: parents.clear();
198: } else {
199: if (previous.getLevel() < gnode.getLevel()) {
200: parents.push(previous);
201: } else if (previous.getLevel() > gnode.getLevel()) {
202: while (parents.size() >= gnode.getLevel())
203: parents.pop();
204: }
205: ((GraphicNode) parents.peek()).getChildren().add(
206: gnode);
207: }
208: previous = gnode;
209: }
210: }
211:
212: //remove parents without children
213: if (preserveHierarchy) {
214: list.clear();
215: if (!transformer.isShowEmptySummaries())
216: filterEmptySummaries(localList, false);
217: }
218:
219: if (sorting) {
220: if (sorter1 != null)
221: sorter1.sortList(localList, new GraphicNodeComparator(
222: sorter1, composition), preserveHierarchy);
223: if (!transformer.isNoneSorter())
224: sorter2.sortList(localList, new GraphicNodeComparator(
225: sorter2, composition), preserveHierarchy);
226: }
227:
228: if (grouping) {
229: List groups = grouper.getGroups();
230: levelOffset = groups.size();
231: List groupedList = new LinkedList();
232: groupList(localList, groupedList, groups.listIterator(),
233: null, composition, preserveHierarchy);
234: localList.clear();
235: localList.addAll(groupedList);
236: }
237:
238: if (preserveHierarchy) { //converts tmp tree to list
239: treeToList(localList, list);
240: }
241:
242: if (assignmentsMap != null)
243: recoverAssignments(list, assignmentsMap);
244:
245: // if (transformer.isShowEmptyLines())
246: // placeVoidNodes(list);
247:
248: }
249:
250: private boolean filterEmptySummaries(List list, boolean filterVoids) {
251: boolean containsNonSummaries = false;
252: boolean containsVoids = false;
253: for (Iterator i = list.iterator(); i.hasNext();) {
254: GraphicNode gnode = (GraphicNode) i.next();
255: if (gnode.isVoid()) {
256: containsVoids = true;
257: continue;
258: }
259: if (!gnode.isSummary()
260: || (gnode.getChildren().size() > 0 && filterEmptySummaries(
261: gnode.getChildren(), true)))
262: containsNonSummaries = true;
263: else
264: i.remove();
265: }
266: if (!containsNonSummaries && (!containsVoids || filterVoids))
267: list.clear();
268: return containsNonSummaries;
269: }
270:
271: private void treeToList(List in, List out) {
272: for (Iterator i = in.iterator(); i.hasNext();) {
273: HierarchicObject gnode = (HierarchicObject) i.next();
274: out.add(gnode);
275: if (gnode.getChildren().size() > 0)
276: treeToList(gnode.getChildren(), out);
277: gnode.getChildren().clear();
278: }
279: }
280:
281: public int getLevelOffset() {
282: return levelOffset;
283: }
284:
285: private class GraphicNodeComparator implements Comparator {
286: protected NodeSorter comparator;
287: protected NodeTransformer composition;
288:
289: private GraphicNodeComparator(NodeSorter comparator) {
290: this (comparator, null);
291: }
292:
293: private GraphicNodeComparator(NodeSorter comparator,
294: NodeTransformer composition) {
295: this .comparator = comparator;
296: this .composition = composition;
297: }
298:
299: public int compare(Object o1, Object o2) {
300: GraphicNode n1 = (GraphicNode) o1;
301: GraphicNode n2 = (GraphicNode) o2;
302: if (n1 == n2)
303: return 0;
304: else if (n1.isVoid())
305: return 1;
306: else if (n2.isVoid())
307: return -1;
308: if (composition == null)
309: return comparator.compare(n1.getNode(), n2.getNode());
310: else
311: return comparator
312: .compare(composition.evaluate(n1.getNode()),
313: composition.evaluate(n2.getNode()));
314: }
315:
316: public ListIterator getCurrentSorter() {
317: return comparator.getCurrentSorter();
318: }
319: }
320:
321: protected static GraphicNode createGroupWithName(int level,
322: NodeGroup group, String name) {
323: Node node = NodeFactory.getInstance().createGroup(group, name);
324: GraphicNode gnode = new GraphicNode(node, level);
325: gnode.setComposite(true);
326: gnode.setSummary(true);
327: gnode.setCollapsed(false);
328: return gnode;
329: }
330:
331: protected GraphicNode createGroup(int level, NodeGroup group,
332: NodeSorter sorter, Node node) {
333: return createGroupWithName(level, group, sorter
334: .getGroupName(node));
335: }
336:
337: private void groupList(List list, List destList,
338: ListIterator groupIterator, Node parentGroup,
339: NodeTransformer composition, boolean preserveHierarchy) {
340: NodeGroup group = (NodeGroup) groupIterator.next();
341: NodeSorter sorter = group.getSorter();
342: GraphicNodeComparator gcomp = new GraphicNodeComparator(sorter,
343: composition);
344: sorter.sortList(list, gcomp, preserveHierarchy);
345: GraphicNode last = null;
346: List nodes = null;
347: GraphicNode current;
348: for (ListIterator i = list.listIterator(); i.hasNext();) {
349: current = (GraphicNode) i.next();
350: if (last == null) {
351: nodes = new LinkedList();
352: } else if (gcomp.compare(last, current) != 0) {
353: handleGroup(destList, groupIterator, parentGroup,
354: group, last, nodes, composition,
355: preserveHierarchy);
356: nodes = new LinkedList();
357: }
358: nodes.add(current);
359: last = current;
360: }
361: if (nodes != null && nodes.size() > 0) {
362: handleGroup(destList, groupIterator, parentGroup, group,
363: last, nodes, composition, preserveHierarchy);
364: }
365: groupIterator.previous();
366: }
367:
368: private void handleGroup(List destList, ListIterator groupIterator,
369: Node parentGroup, NodeGroup group, GraphicNode last,
370: List nodes, NodeTransformer composition,
371: boolean preserveHierarchy) {
372: GraphicNode groupNode = createGroup(groupIterator.nextIndex(),
373: group, group.getSorter(), last.getNode());
374: destList.add(groupNode);
375: model.addRelationship(parentGroup, groupNode.getNode());
376: if (groupIterator.hasNext()) {
377: groupList(nodes, destList, groupIterator, groupNode
378: .getNode(), composition, preserveHierarchy);
379: } else {
380: for (Iterator j = nodes.iterator(); j.hasNext();)
381: model.addRelationship(groupNode.getNode(),
382: ((GraphicNode) j.next()).getNode());
383: destList.addAll(nodes);
384: }
385: }
386:
387: private void placeVoidNodes(List list) {
388: ListIterator i = list.listIterator();
389: while (i.hasNext()) {
390: GraphicNode gnode = (GraphicNode) i.next();
391: //if (!gnode.isGroup()){ //Already disabled if sorters or groupers are working
392: placeVoidNodes(i, gnode.getNode());
393: //}
394: }
395: placeVoidNodes(i, (Node) refCache.getModel().getRoot());
396: }
397:
398: private void placeVoidNodes(ListIterator i, Node node) {
399: Node current;
400: for (Enumeration e = node.children(); e.hasMoreElements();) {
401: current = (Node) e.nextElement();
402: if (current.isVoid()) {
403: GraphicNode gcurrent = refCache.getGraphicNode(current);
404: i.add(gcurrent);
405: }
406: }
407: }
408:
409: // private void placeVoidNodes(ListIterator i,int maxLevel){
410: // List nodes;
411: // GraphicNode previous,child;
412: // Iterator j;
413: // List parentLevelVoidNodes=null;
414: //
415: // while (i.hasNext()){
416: // previous=(GraphicNode)i.next();
417: // if (previous.getLevel()<=maxLevel){
418: // i.previous();
419: // return;
420: // }
421: // nodes=refCache.getVoidNodes(previous);
422: // if (nodes!=null)
423: // for (j=nodes.iterator();j.hasNext();){
424: // child=(GraphicNode)j.next();
425: // if (child.getLevel()>previous.getLevel()){
426: // if (!(previous.isComposite()&&previous.isCollapsed())) i.add(child);
427: // }
428: // else{
429: // if (parentLevelVoidNodes==null)
430: // parentLevelVoidNodes=new LinkedList();
431: // parentLevelVoidNodes.add(child);
432: // }
433: // }
434: // if (parentLevelVoidNodes!=null){ //adds voids nodes at previous level
435: // placeVoidNodes(i,previous.getLevel());
436: // for (j=parentLevelVoidNodes.iterator();j.hasNext();)
437: // i.add(j.next());
438: // parentLevelVoidNodes=null;
439: // }
440: // }
441: // }
442: //
443: // private void placeVoidNodes(List list){
444: // placeVoidNodes(list.listIterator(),0);
445: //
446: // List nodes=refCache.getVoidNodes(NodeCache.BEGIN_VOIDNODES);
447: // if (nodes!=null) list.addAll(0,nodes);
448: //
449: // nodes=refCache.getVoidNodes(NodeCache.END_VOIDNODES);
450: // if (nodes!=null) list.addAll(nodes);
451: //
452: //
453: // }
454:
455: private void removeVoids(List list) {
456: GraphicNode current;
457: for (ListIterator i = list.listIterator(); i.hasNext();) {
458: current = (GraphicNode) i.next();
459: if (current.isVoid()) {
460: i.remove();
461: }
462: }
463: }
464:
465: private void removeEndVoids(List list) {
466: GraphicNode current;
467: for (ListIterator i = list.listIterator(list.size()); i
468: .hasPrevious();) {
469: current = (GraphicNode) i.previous();
470: if (current.isVoid()) {
471: i.remove();
472: } else
473: break;
474: }
475: }
476:
477: private void removeSummaries(List list) {
478: GraphicNode current;
479: for (ListIterator i = list.listIterator(); i.hasNext();) {
480: current = (GraphicNode) i.next();
481: if (current.isSummary()) {
482: i.remove();
483: }
484: }
485: }
486:
487: private void removeAssignments(List list) {
488: GraphicNode current;
489: for (ListIterator i = list.listIterator(); i.hasNext();) {
490: current = (GraphicNode) i.next();
491: if (current.isAssignment()) {
492: i.remove();
493: }
494: }
495: }
496:
497: private Map<GraphicNode, List<GraphicNode>> extractAssignments(
498: List list) {
499: Map<GraphicNode, List<GraphicNode>> map = new HashMap<GraphicNode, List<GraphicNode>>();
500: GraphicNode current, last;
501: Stack<GraphicNode> path = new Stack<GraphicNode>();
502: for (ListIterator i = list.listIterator(); i.hasNext();) {
503: current = (GraphicNode) i.next();
504: if (current.getLevel() == 1) {
505: path.clear();
506: path.push(current);
507: continue;
508: }
509: while ((last = path.peek()).getLevel() >= current
510: .getLevel())
511: path.pop();
512: if (current.isAssignment()) {
513: GraphicNode task = path.peek();
514: List<GraphicNode> ass = map.get(task);
515: if (ass == null) {
516: ass = new LinkedList<GraphicNode>();
517: map.put(task, ass);
518: }
519: ass.add(current);
520: i.remove();
521: }
522: path.push(current);
523: }
524: return map;
525: }
526:
527: private void recoverAssignments(List list,
528: Map<GraphicNode, List<GraphicNode>> map) {
529: GraphicNode current = null;
530: for (ListIterator i = list.listIterator(); i.hasNext();) {
531: current = (GraphicNode) i.next();
532: if (map.containsKey(current)) {
533: List<GraphicNode> ass = map.get(current);
534: for (GraphicNode a : ass) {
535: i.add(a);
536: }
537: }
538: }
539: }
540:
541: public WalkersNodeModel getWalkersModel() {
542: return model;
543: }
544:
545: private TransformerNodeModel model = new TransformerNodeModel();
546:
547: class TransformerNodeModel implements WalkersNodeModel {
548: protected MultiMap childrenMap = new MultiHashMap();
549: protected Map parentMap = new HashMap();
550:
551: public List getChildren(Node node) {
552: if (node.getImpl() instanceof GroupNodeImpl) {
553: return (List) childrenMap.get(node);
554: } else
555: return refCache.getModel().getChildren(node);
556: }
557:
558: public Node getParent(Node child) {
559: if (parentMap.containsKey(child)) {
560: return (Node) parentMap.get(child);
561: } else
562: return refCache.getModel().getParent(child);
563: }
564:
565: public boolean isSummary(Node node) {
566: if (node.getImpl() instanceof GroupNodeImpl)
567: return true;
568: else
569: return refCache.getModel().isSummary(node);
570: }
571:
572: public Node search(Object key) {
573: Node node = refCache.getModel().search(key);
574: return null;
575: }
576:
577: public void addRelationship(Node parent, Node child) {
578: if (parent != null)
579: childrenMap.put(parent, child);
580: parentMap.put(child, parent);
581: }
582:
583: public void clear() {
584: childrenMap.clear();
585: parentMap.clear();
586: }
587:
588: public Document getDocument() {
589: return refCache.getDocument();
590: }
591: }
592:
593: }
|