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.cache;
051:
052: import java.util.ArrayList;
053: import java.util.Collection;
054: import java.util.HashMap;
055: import java.util.HashSet;
056: import java.util.Iterator;
057: import java.util.LinkedList;
058: import java.util.List;
059: import java.util.Map;
060: import java.util.Set;
061:
062: import com.projity.association.AssociationList;
063: import com.projity.association.InvalidAssociationException;
064: import com.projity.document.Document;
065: import com.projity.document.ObjectEvent;
066: import com.projity.graphic.configuration.GraphicConfiguration;
067: import com.projity.grouping.core.Node;
068: import com.projity.grouping.core.event.HierarchyEvent;
069: import com.projity.grouping.core.event.HierarchyListener;
070: import com.projity.grouping.core.hierarchy.NodeHierarchy;
071: import com.projity.grouping.core.model.NodeModel;
072: import com.projity.pm.assignment.Assignment;
073: import com.projity.pm.dependency.Dependency;
074: import com.projity.pm.dependency.DependencyService;
075: import com.projity.pm.dependency.DependencyType;
076: import com.projity.pm.dependency.HasDependencies;
077: import com.projity.pm.resource.Resource;
078: import com.projity.pm.scheduling.ScheduleEvent;
079: import com.projity.pm.scheduling.ScheduleEventListener;
080: import com.projity.pm.task.Project;
081: import com.projity.pm.task.Task;
082:
083: /**
084: * This class lies between the SpreadSheet and the SpreadSheetModel.
085: * It holds the states directly linked to the view.
086: * The collapsed state and level of the nodes here.
087: * The level is not a view state but it is calculated and cached for performance purposes.
088: */
089:
090: public class ReferenceNodeModelCache implements ObjectEvent.Listener,
091: HierarchyListener, /*TreeModel,*/ScheduleEventListener {
092: private NodeModel model;
093:
094: protected NodeCache nodeCache;
095: protected DependencyCache edgeCache;
096: protected Document document;
097:
098: protected int type;
099:
100: /**
101: * @param model
102: */
103: public ReferenceNodeModelCache(NodeModel model, Document document,
104: int type) {
105: this .document = document;
106: nodeCache = new NodeCache();
107: edgeCache = new DependencyCache();
108: setModel(model);
109: this .type = type;
110: }
111:
112: // public ReferenceNodeModelCache(NodeModel model) {
113: // this(model,null);
114: // }
115: /**
116: *
117: */
118: // public ReferenceNodeModelCache(Document document) {
119: // this.document = document;
120: // nodeCache=new NodeCache();
121: // edgeCache=new DependencyCache();
122: // }
123: public int getType() {
124: return type;
125: }
126:
127: public void setType(int type) {
128: this .type = type;
129: }
130:
131: public GraphicNode getGraphicNode(Node node) {
132: return (GraphicNode) nodeCache.getElement(node);
133: }
134:
135: public void bindView(VisibleNodes nodes, VisibleDependencies deps) {
136: nodeCache.addVisibleElements(nodes);
137: edgeCache.addVisibleElements(deps);
138: //updateVisibleElements(nodes,deps,new HashSet());
139: //updateVisibleElements(nodes,new HashSet());
140: }
141:
142: public void unbindView(VisibleNodes nodes, VisibleDependencies deps) {
143: nodeCache.addVisibleElements(nodes);
144: edgeCache.addVisibleElements(deps);
145: }
146:
147: public void close() {
148: if (model != null) {
149: removeListeners();
150: nodeCache.removeAllVisibleElements();
151: nodeCache.clear();
152: edgeCache.removeAllVisibleElements();
153: edgeCache.clear();
154: }
155: }
156:
157: protected void finalize() throws Throwable {
158: super .finalize();
159: close();
160: }
161:
162: private void removeListeners() {
163: model.getHierarchy().removeHierarchyListener(this );
164: if (document != null)
165: document.removeObjectListener(this );
166: if (document != null && document instanceof Project)
167: ((Project) document).removeScheduleListener(this );
168: }
169:
170: public Document getDocument() {
171: return document;
172: }
173:
174: public GraphicNode getParent(GraphicNode node) {
175: Node parent = getModel().getHierarchy().getParent(
176: node.getNode()); //can be null
177: return (GraphicNode) nodeCache.getElement(parent);
178: }
179:
180: public List getChildren(GraphicNode node) {
181: Collection children = getModel().getHierarchy().getChildren(
182: (node == null) ? null : node.getNode());
183: if (children == null)
184: return null;
185: List list = new LinkedList();//new ArrayList(children.size());
186: Object child;
187: for (Iterator i = children.iterator(); i.hasNext();) {
188: child = nodeCache.getElement(i.next());
189: if (child != null)
190: list.add(child);
191: }
192: return list;
193: }
194:
195: public List getEdges() {
196: return edgeCache.getCache();
197: }
198:
199: public Object getGraphicNode(Object base) {
200: return nodeCache.getElement(base);
201: }
202:
203: public Object getGraphicDependency(Object base) {
204: return edgeCache.getElement(base);
205: }
206:
207: //update
208: public void update() {
209: update(new HashSet(), false);
210: }
211:
212: public void update(boolean reschedule) {
213: update(new HashSet(), reschedule);
214: }
215:
216: public void update(Set change, boolean reschedule) {
217: // System.out.println("ReferenceNodeModelCache update");
218: NodeCache newCache = new NodeCache();
219: update(null, newCache, change, reschedule);
220:
221: //edges
222: Set edgeChange = new HashSet();
223: updateEdges(edgeChange);
224:
225: nodeCache.copyContent(newCache);
226:
227: updateVisibleElements(change, edgeChange);
228: }
229:
230: protected void updateVisibleElements(Set change, Set edgeChange) {
231: // long t0=System.currentTimeMillis();
232: nodeCache.updateVisibleElements(change);
233: // long t1=System.currentTimeMillis();
234: // System.out.println("\tcache nodeCache.updateVisibleElements ran in "+(t1-t0)+"ms");
235:
236: edgeCache.updateVisibleElements(edgeChange);
237: // t0=System.currentTimeMillis();
238: // System.out.println("\tcache edgeCache.updateVisibleElements ran in "+(t0-t1)+"ms");
239:
240: nodeCache.fireEvents(this );
241: // t1=System.currentTimeMillis();
242: // System.out.println("\tcache nodeCache.fireEvents ran in "+(t1-t0)+"ms");
243:
244: }
245:
246: protected void updateVisibleElements(VisibleNodes nodes/*,Set change*/) {
247: nodeCache.updateVisibleElements(nodes, new HashSet());
248: edgeCache.updateVisibleElements(nodes.getVisibleDependencies(),
249: new HashSet());
250: nodeCache.fireEvents(this , nodes);
251: }
252:
253: public void updateEdges(Set change) {
254: GraphicDependency current;
255: for (Iterator i = edgeCache.getCacheIterator(); i.hasNext();) {
256: current = (GraphicDependency) i.next();
257: if (current.isDirty()) {
258: current.setDirty(false);
259: change.add(current);
260: }
261: }
262: }
263:
264: public void update(GraphicNode node, NodeCache newCache,
265: Set change, boolean reschedule) {
266: int level = (node == null) ? 0 : node.getLevel();
267:
268: int collapseLevel = GraphicConfiguration.getInstance()
269: .getCollapseLevel();
270:
271: GraphicNode current;
272: Collection children = model.getHierarchy().getChildren(
273: (node == null) ? null : node.getNode());
274: boolean summary = false;
275: if (children != null) {
276: Node child;
277: for (Iterator i = children.iterator(); i.hasNext();) {
278: child = (Node) i.next();
279: Object impl = child.getImpl();
280: if (!(impl instanceof Assignment))
281: summary = true;
282: current = (GraphicNode) nodeCache.getElement(child);
283: if (current == null) {
284: current = createNode(child);
285: if (collapseLevel != -1
286: && level >= collapseLevel - 1)
287: current.setCollapsed(true);
288: }
289: newCache.insertElement(current, current.getNode());
290: if (current.getLevel() != level + 1) {
291: current.setLevel(level + 1);
292: }
293: if (current.isVoid() && (!current.getNode().isVoid())) {
294: current.setVoid(false);
295: } else if (!current.isVoid()
296: && (current.getNode().isVoid())) {
297: current.setVoid(true);
298: }
299: if (reschedule && impl instanceof Task
300: && ((Task) impl).isJustModified()) {
301: current.setDirty(true);
302: }
303: if (current.isDirty()) {
304: change.add(current);
305: current.setDirty(false);
306: }
307: update(current, newCache, change, reschedule);
308: }
309: }
310:
311: if (node != null) {
312: boolean composite = !(children == null || children.size() == 0);
313: if (node.isComposite() != composite) {
314: node.setComposite(composite);
315: }
316: if (node.isSummary() != summary) {
317: node.setSummary(summary);
318: }
319:
320: if (node.isDirty()) {
321: change.add(node);
322: node.setDirty(false);
323: }
324:
325: node.updateScheduleCache();
326: }
327:
328: }
329:
330: /**
331: * @return Returns the model.
332: */
333: public NodeModel getModel() {
334: return model;
335: }
336:
337: /**
338: * @param model The model to set.
339: */
340: public void setModel(NodeModel model) {
341: if (this .model != null) {
342: removeListeners();
343: }
344: this .model = model;//new FilteredNodeModel(model);
345: //this.model.setFilter(filter);
346: model.getHierarchy().addHierarchyListener(this );
347: if (document != null)
348: document.addObjectListener(this );
349: if (document != null && document instanceof Project)
350: ((Project) document).addScheduleListener(this );
351: buildCache();
352: }
353:
354: private void buildCache() {
355: nodeCache.clear();
356: getModel().getHierarchy().checkEndVoidNodes(NodeModel.SILENT);
357: update();
358: buildEdges();
359: syncEdges();
360: }
361:
362: public void changeCollapsedState(GraphicNode gnode) {
363: if (gnode.isComposite())
364: gnode.setCollapsed(!gnode.isCollapsed());
365: update();
366: }
367:
368: //edges
369: public void buildEdges() {
370: Map implMap = new HashMap();
371: List gnodes = new ArrayList();
372: for (Iterator i = nodeCache.getCache().iterator(); i.hasNext();) {
373: GraphicNode gnode = (GraphicNode) i.next();
374: if (gnode.isVoid() || gnode.isAssignment())
375: continue;
376: if (!(gnode.getNode().getImpl() instanceof HasDependencies))
377: return; //TODO this is ugly. perhaps subclass NodeBridge for cases when impl has dependencies
378: gnodes.add(gnode);
379: implMap.put(gnode.getNode().getImpl(), gnode);
380: }
381:
382: for (Iterator i = gnodes.iterator(); i.hasNext();) {
383: GraphicNode gnode = (GraphicNode) i.next();
384:
385: HasDependencies task = (HasDependencies) gnode.getNode()
386: .getImpl();
387: AssociationList dependencyList = task.getSuccessorList();
388: for (Iterator j = dependencyList.iterator(); j.hasNext();) {
389: Dependency dep = (Dependency) j.next();
390:
391: HasDependencies pre = dep.getPredecessor();
392: HasDependencies suc = dep.getSuccessor();
393: GraphicNode preGNode = (GraphicNode) implMap.get(pre);
394: GraphicNode sucGNode = (GraphicNode) implMap.get(suc);
395: if (preGNode != null && sucGNode != null) {
396: newGraphicDependency(preGNode, sucGNode, dep);
397: } else {
398: System.out.println("no graphic node");
399: }
400: }
401: }
402: }
403:
404: public GraphicDependency newGraphicDependency(GraphicNode preGNode,
405: GraphicNode sucGNode, Dependency dep) {
406: GraphicDependency gdep = new GraphicDependency(preGNode,
407: sucGNode, dep);
408: int depType = dep.getDependencyType();
409: //gdep.setType(depType);
410: edgeCache.insertElement(gdep, dep);
411: return gdep;
412: }
413:
414: private void syncEdges() {
415: edgeCache.updateAllVisibleElements();
416: }
417:
418: public void createDependency(GraphicNode startNode,
419: GraphicNode endNode) throws InvalidAssociationException {
420: DependencyService service = DependencyService.getInstance();
421: HasDependencies startObject = (HasDependencies) startNode
422: .getNode().getImpl();
423: HasDependencies endObject = (HasDependencies) endNode.getNode()
424: .getImpl();
425: //try {
426: Dependency dep = service.newDependency(startObject, endObject,
427: DependencyType.FS, 0L, this );
428: //} catch (InvalidAssociationException e) {
429: // e.printStackTrace();
430: //}
431: }
432:
433: // public void createHierarchyDependency(GraphicNode startNode,GraphicNode endNode){
434: // model.getHierarchy().move(endNode.getNode(),startNode.getNode());
435: // }
436:
437: public void removeEdge(GraphicDependency dep) {
438: if (dep == null)
439: return;
440: edgeCache.deleteElement(dep);
441: }
442:
443: public void modifyEdge(GraphicDependency dep, int type) {
444: if (type != -1) {
445: //dep.setType(type);
446: }
447: }
448:
449: protected int getLevel(Node node) {
450: int level = 0;
451: NodeHierarchy hierarchy = getModel().getHierarchy();
452: for (Node current = node; current != null; current = hierarchy
453: .getParent(current))
454: level++;
455: return level;
456: }
457:
458: protected boolean isComposite(Node node) {
459: return !getModel().getHierarchy().isLeaf(node);
460: }
461:
462: protected boolean isSummary(Node node) {
463: return getModel().getHierarchy().isSummary(node);
464: }
465:
466: public GraphicNode createNode(Node node) {
467: return new GraphicNode(node, -1);
468: }
469:
470: protected boolean receiveEvents = true;
471:
472: public boolean isReceiveEvents() {
473: return receiveEvents;
474: }
475:
476: public void setReceiveEvents(boolean receiveEvents) {
477: this .receiveEvents = receiveEvents;
478: }
479:
480: public void scheduleChanged(ScheduleEvent e) {
481: //System.out.println("ScheduleEvent: type="+e.getType()+", snap="+e.getSnapshot()+", object="+e.getObject());
482: if (!receiveEvents)
483: return;
484: // nodeCache.updateCachedSchedule();
485: // nodeCache.fireScheduleEvent(e.getSource(),e);
486: update(true);
487: }
488:
489: public void objectChanged(ObjectEvent objectEvent) {
490: //System.out.println("ObjectEvent: type="+objectEvent.getType()+", field="+objectEvent.getField()+", object="+objectEvent.getObject());
491: if (!receiveEvents)
492: return;
493: Object object = objectEvent.getObject();
494: if (object instanceof Dependency) {
495: Dependency dependency = ((Dependency) object);
496: if (dependency.getDocument() == document
497: || dependency.getMasterDocument() == document) { // links can come from other projects too, but successor should be in this project
498: if (objectEvent.isCreate()) {
499: Node preNode = (Node) model.search(dependency
500: .getPredecessor());
501: Node sucNode = (Node) model.search(dependency
502: .getSuccessor());
503: GraphicNode preGNode = (GraphicNode) nodeCache
504: .getElement(preNode);
505: GraphicNode sucGNode = (GraphicNode) nodeCache
506: .getElement(sucNode);
507: if (preGNode != null && sucGNode != null) {
508: GraphicDependency edge = (GraphicDependency) edgeCache
509: .getElement(dependency);
510: if (edge == null) { // for external tasks in subprojects, it's possible they already were created
511: edge = newGraphicDependency(preGNode,
512: sucGNode, dependency);
513: update();
514: }
515: }
516: } else if (objectEvent.isDelete()) {
517: GraphicDependency edge = (GraphicDependency) edgeCache
518: .getElement(dependency);
519: if (edge != null) {
520: removeEdge(edge);
521: update();
522: }
523: //edgeCache.fireEdgesRemoved(this,new Object[]{edge});
524: } else { //update
525: GraphicDependency edge = (GraphicDependency) edgeCache
526: .getElement(dependency);
527: if (edge != null) {
528: modifyEdge(edge, dependency.getDependencyType());
529: update();
530: }
531: //edgeCache.fireEdgesUpdated(this,new Object[]{edge});
532: }
533: }
534: } else {
535: if (object != null
536: && ((object instanceof Task && (type & NodeModelCache.TASK_TYPE) == NodeModelCache.TASK_TYPE)
537: || (object instanceof Resource && (type & NodeModelCache.RESOURCE_TYPE) == NodeModelCache.RESOURCE_TYPE)
538: || (object instanceof Assignment && (type & NodeModelCache.ASSIGNMENT_TYPE) == NodeModelCache.ASSIGNMENT_TYPE) || (object instanceof Project && (type & NodeModelCache.PROJECT_TYPE) == NodeModelCache.PROJECT_TYPE))) {
539: if (object != null && !objectEvent.isDelete()) { //because node is already deleted
540: //TODO cannot update parents in case of deletion
541: Node node = model.search(object);
542: if (node != null) {
543: for (; !node.isRoot(); node = model
544: .getParent(node)) {
545: // System.out.println("objectChanged "+objectEvent.getType()+": "+node);
546: GraphicNode gnode = getGraphicNode(node);
547: if (gnode != null) // on project list it is null
548: gnode.setDirty(true);
549: //nodeCache.fireObjectEvent(objectEvent.getSource(),objectEvent);
550: }
551: }
552: }
553: update();
554: }
555:
556: }
557: }
558:
559: public void nodesChanged(HierarchyEvent e) {
560: if (receiveEvents && !e.isConsumed())
561: update();
562: }
563:
564: public void nodesInserted(HierarchyEvent e) {
565: if (receiveEvents && !e.isConsumed())
566: update();
567: }
568:
569: public void nodesRemoved(HierarchyEvent e) {
570: if (receiveEvents && !e.isConsumed())
571: update();
572: }
573:
574: public void structureChanged(HierarchyEvent e) {
575: if (receiveEvents && !e.isConsumed())
576: update();
577: }
578:
579: // public void addTreeModelListener(TreeModelListener arg0) {
580: // model.addTreeModelListener(arg0);
581: // }
582: // public Object getChild(Object arg0, int arg1) {
583: // return model.getChild(arg0, arg1);
584: // }
585: // public int getChildCount(Object arg0) {
586: // return model.getChildCount(arg0);
587: // }
588: // public int getIndexOfChild(Object arg0, Object arg1) {
589: // return model.getIndexOfChild(arg0, arg1);
590: // }
591:
592: protected GraphicNode root = null;
593:
594: public Object getRoot() {
595: if (root == null)
596: root = new GraphicNode((Node) model.getRoot(), 0);
597: return root;
598: }
599:
600: // public boolean isLeaf(Object arg0) {
601: // return model.isLeaf(arg0);
602: // }
603: // public void removeTreeModelListener(TreeModelListener arg0) {
604: // model.removeTreeModelListener(arg0);
605: // }
606: // public void valueForPathChanged(TreePath arg0, Object arg1) {
607: // model.valueForPathChanged(arg0, arg1);
608: // }
609:
610: public String toString() {
611: return nodeCache.getVisibleElements().toString();
612: }
613:
614: }
|