View Javadoc

1   package org.kite9.diagram.builders;
2   
3   import java.lang.reflect.AnnotatedElement;
4   import java.lang.reflect.Method;
5   import java.util.ArrayList;
6   import java.util.Collection;
7   import java.util.Collections;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Map;
11  
12  import org.kite9.diagram.adl.Arrow;
13  import org.kite9.diagram.adl.Context;
14  import org.kite9.diagram.adl.Diagram;
15  import org.kite9.diagram.adl.Glyph;
16  import org.kite9.diagram.adl.Key;
17  import org.kite9.diagram.adl.KeyHelper;
18  import org.kite9.diagram.adl.Link;
19  import org.kite9.diagram.adl.LinkEndStyle;
20  import org.kite9.diagram.adl.Symbol;
21  import org.kite9.diagram.adl.TextLine;
22  import org.kite9.diagram.annotation.K9OnDiagram;
23  import org.kite9.diagram.builders.Relationship.RelationshipType;
24  import org.kite9.diagram.builders.ValueHelper.ToEnd;
25  import org.kite9.diagram.position.Direction;
26  import org.kite9.diagram.primitives.Connected;
27  import org.kite9.diagram.primitives.Contained;
28  import org.kite9.diagram.primitives.Container;
29  import org.kite9.diagram.primitives.DiagramElement;
30  import org.kite9.framework.alias.Aliaser;
31  import org.kite9.framework.common.HelpMethods;
32  import org.kite9.framework.common.Kite9ProcessingException;
33  import org.kite9.framework.model.ProjectModel;
34  
35  /***
36   * Builds a diagram from a method. Each object from the java project may only be
37   * represented once in the diagram.
38   * 
39   * @author robmoffat
40   * 
41   */
42  public class DiagramBuilder {
43  
44      protected IdHelper idHelper = new IdHelper();
45      protected ValueHelper typeHelper = new ValueHelper();
46      protected KeyHelper kh = new KeyHelper();
47      protected ProjectModel pm;
48  
49      private Aliaser a;
50      private Method creator;
51      private Diagram d;
52      private Map<Object, DiagramElement> contents = new HashMap<Object, DiagramElement>();
53  
54      public DiagramBuilder(Aliaser a, Method creator, ProjectModel pm) {
55  	this.a = a;
56  	this.d = createRepresentation(getId(creator));
57  	this.creator = creator;
58  	this.pm = pm;
59      }
60  
61      public ProjectModel getProjectModel() {
62  	return pm;
63      }
64  
65      public Diagram getDiagram() {
66  	addKey();
67  	return d;
68      }
69  
70      public Diagram createRepresentation(String id) {
71  	return new Diagram(id, new ArrayList<Contained>(), null);
72      }
73  
74      private void addKey() {
75  	Collection<Symbol> syms = kh.getUsedSymbols();
76  	if (syms.size() > 0) {
77  	    List<Symbol> ordered = new ArrayList<Symbol>(syms);
78  	    Collections.sort(ordered);
79  
80  	    if (d.getKey() == null) {
81  		Key k = new Key(null, null, ordered);
82  		d.setKey(k);
83  	    } else {
84  		d.getKey().setSymbols(ordered);
85  	    }
86  
87  	}
88      }
89  
90      public DiagramBuilder withKeyText(String boldtext, String body) {
91  	Key k = new Key(boldtext, body, null);
92  	d.setKey(k);
93  	return this;
94      }
95  
96      public String getId(Object o) {
97  	return idHelper.getId(o);
98      }
99  
100     private Container getWithin(Object o, Relationship rel) {
101 	if (o == null)
102 	    return d;
103 
104 	DiagramElement within = contents.get(o);
105 	if (within == null) {
106 	    // no context to place the element in, so put in the diagram.
107 	    return d;
108 	}
109 
110 	if (rel instanceof HasRelationship) {
111 	    if (within instanceof Container) {
112 		return (Container) within;
113 	    } else if (within instanceof Contained) {
114 		return ((Contained) within).getContainer();
115 	    } else {
116 		throw new Kite9ProcessingException("Cannot find container for " + within);
117 	    }
118 	} else {
119 	    // object must exist outside context
120 	    if (within instanceof Contained) {
121 		return ((Contained) within).getContainer();
122 	    } else {
123 		throw new Kite9ProcessingException("Cannot find container for " + within);
124 	    }
125 	}
126     }
127 
128     /***
129      * Looks for an existing diagram element, or returns a new glyph
130      */
131     private DiagramElement createGlyph(Object withinOb, Relationship rel, Object value, String stereo) {
132 	Container within = getWithin(withinOb, rel);
133 
134 	DiagramElement out = contents.get(value);
135 	if (out == null) {
136 	    String id = idHelper.getId(value);
137 	    String label = a.getObjectAlias(value);
138 	    out = new Glyph(id, stereo, label, null, null);
139 	    contents.put(value, out);
140 	    within.getContents().add((Glyph) out);
141 	    ((Glyph) out).setContainer(within);
142 	}
143 	return out;
144     }
145 
146     private DiagramElement createContext(Object withinOb, Object value, Relationship rel, boolean border, Direction d) {
147 	Container within = getWithin(withinOb, rel);
148 
149 	DiagramElement out = contents.get(value);
150 	if (out == null) {
151 	    String id = idHelper.getId(value);
152 	    String label = a.getObjectAlias(value);
153 	    out = new Context(id, null, border, new TextLine(label), d);
154 	    contents.put(value, out);
155 	    within.getContents().add((Context) out);
156 	    ((Context) out).setContainer(within);
157 	}
158 	return out;
159     }
160 
161     private DiagramElement createArrow(Object withinOb, Object value, Relationship rel) {
162 	Container within = getWithin(withinOb, rel);
163 
164 	DiagramElement out = contents.get(value);
165 	if (out == null) {
166 	    String id = idHelper.getId(value);
167 	    String label = a.getObjectAlias(value);
168 	    out = new Arrow(id, label);
169 	    contents.put(value, out);
170 	    within.getContents().add((Arrow) out);
171 	    ((Arrow) out).setContainer(within);
172 	}
173 	return out;
174     }
175 
176     public Format asGlyphs() {
177 	return new Format() {
178 	    public void write(Object context, Relationship key, Object value) {
179 		ToEnd te = typeHelper.setupToEnd(value, a);
180 		DiagramElement ctxEl = contents.get(context);
181 		String stereo = a.getObjectTypeAlias(te.to);
182 		DiagramElement valueEl = createGlyph(context, key, te.to, stereo);
183 		checkAddRelationshipLink((Connected) ctxEl, (Connected) valueEl, key, te, context);
184 	    }
185 	};
186     }
187 
188     public Format asGlyphs(final String withStereotype) {
189 	return new Format() {
190 	    public void write(Object context, Relationship key, Object value) {
191 		ToEnd te = typeHelper.setupToEnd(value, a);
192 		DiagramElement ctxEl = contents.get(context);
193 		DiagramElement valueEl = createGlyph(context, key, te.to, withStereotype);
194 		checkAddRelationshipLink((Connected) ctxEl, (Connected) valueEl, key, te, context);
195 	    }
196 	};
197     }
198 
199     public Format asContexts() {
200 	return asContexts(true, null);
201     }
202 
203     public Format asContexts(final boolean border, final Direction d) {
204 	return new Format() {
205 	    public void write(Object context, Relationship key, Object value) {
206 		ToEnd te = typeHelper.setupToEnd(value, a);
207 		DiagramElement ctxEl = contents.get(context);
208 		DiagramElement valueEl = createContext(context, te.to, key, border, d);
209 		checkAddRelationshipLink((Connected) ctxEl, (Connected) valueEl, key, te, context);
210 	    }
211 	};
212     }
213 
214     public Format asArrows() {
215 	return new Format() {
216 	    public void write(Object context, Relationship key, Object value) {
217 		ToEnd te = typeHelper.setupToEnd(value, a);
218 		DiagramElement ctxEl = contents.get(context);
219 		DiagramElement valueEl = createArrow(context, te.to, key);
220 		checkAddRelationshipLink((Connected) ctxEl, (Connected) valueEl, key, te, context);
221 	    }
222 	};
223     }
224 
225     protected void checkAddRelationshipLink(Connected ctxEl, Connected valueEl, Relationship key, ToEnd te, Object ctx) {
226 	if (ctxEl == null) {
227 	    // at the diagram level, so no link needed.
228 	    return;
229 	}
230 
231 	// if there is containment, a relationship is already implied
232 	if ((ctxEl instanceof Container) && (((Container) ctxEl).getContents().contains(valueEl))) {
233 	    return;
234 	}
235 
236 	// otherwise, we need to set one up
237 	if ((ctxEl instanceof Arrow) && (valueEl instanceof Arrow)) {
238 	    throw new Kite9ProcessingException("Can't currently handle connecting two arrows " + ctxEl + " " + valueEl);
239 	}
240 
241 	// check for passive relationships. we need to spin those around
242 	if (key.getType() == RelationshipType.PASSIVE) {
243 	    key = key.getActiveRelationship();
244 	    Connected temp = valueEl;
245 	    valueEl = ctxEl;
246 	    ctxEl = temp;
247 	}
248 
249 	LinkEndStyle objectEndStyle = key.getType() == RelationshipType.ACTIVE ? LinkEndStyle.ARROW : null;
250 
251 	String keyAlias = a.getObjectAlias(key);
252 	String textPart = typeHelper.generateArrowLabel(te);
253 	String alias = keyAlias + ((textPart.length() > 0) ? ": " + textPart : "");
254 	if (ctxEl instanceof Arrow) {
255 	    if (!ctxEl.isConnectedDirectlyTo(valueEl))
256 		new Link(ctxEl, valueEl, null, null, null, new TextLine(alias), null);
257 	} else if (valueEl instanceof Arrow) {
258 	    if (!ctxEl.isConnectedDirectlyTo(valueEl))
259 		new Link(valueEl, ctxEl, null, null, null, new TextLine(alias), null);
260 	} else {
261 	    // tied stuff
262 	    SubjectBinding<Object> t = new SubjectBinding<Object>(ctx, key);
263 	    DiagramElement arrowde = createArrow(ctx, t, key);
264 	    if (arrowde instanceof Connected) {
265 		Connected arrow = (Connected) arrowde;
266 		if (!ctxEl.isConnectedDirectlyTo(arrow))
267 		    new Link(ctxEl, arrow);
268 		if (!arrow.isConnectedDirectlyTo(valueEl))
269 		    new Link(arrow, valueEl, null, null, objectEndStyle, new TextLine(textPart), null);
270 	    } else {
271 		throw new Kite9ProcessingException("Item already exists on diagram, but is not an arrow: " + t);
272 	    }
273 	}
274     }
275 
276     public Format asTextLines(final boolean keyAsSymbol) {
277 	return new Format() {
278 	    public void write(Object context, Relationship key, Object value) {
279 		DiagramElement conEl = contents.get(context);
280 		ToEnd te = typeHelper.setupToEnd(value, a);
281 		String alias = a.getObjectAlias(te.to);
282 		String fullText = typeHelper.generateTextLineLabel(te, alias);
283 		String keyAlias = a.getObjectAlias(key);
284 
285 		if (conEl instanceof Glyph) {
286 		    // add a text line to the glyph
287 		    TextLine tl;
288 
289 		    if (keyAsSymbol) {
290 			Symbol out = kh.createSymbol(keyAlias, null);
291 			tl = new TextLine(alias, HelpMethods.createList(out));
292 		    } else {
293 			tl = new TextLine(keyAlias + ": " + fullText, null);
294 		    }
295 
296 		    contents.put(value, tl);
297 
298 		    ((Glyph) conEl).getText().add(tl);
299 		} else if (conEl instanceof TextLine) {
300 		    // add further information to the text line
301 		    TextLine tl = (TextLine) conEl;
302 
303 		    String text = tl.getText();
304 		    text += ", " + keyAlias + ": " + fullText;
305 		    tl.setText(text);
306 
307 		    contents.put(value, tl);
308 		} else {
309 		    throw new Kite9ProcessingException("Text line can only be added to existing text lines or glpyhs: "
310 			    + conEl);
311 		}
312 	    }
313 	};
314     }
315 
316     public Format asSymbols() {
317 	return new Format() {
318 
319 	    public void write(Object context, Relationship key, Object value) {
320 		DiagramElement de = contents.get(context);
321 		ToEnd te = typeHelper.setupToEnd(value, a);
322 		String alias = a.getObjectAlias(te.to);
323 		alias = typeHelper.generateTextLineLabel(te, alias);
324 		String keyAlias = a.getObjectAlias(key);
325 		Symbol out = kh.createSymbol(keyAlias + ": " + alias, alias);
326 		if (de instanceof TextLine) {
327 		    ((TextLine) de).getSymbols().add(out);
328 		} else if (de instanceof Glyph) {
329 		    ((Glyph) de).getSymbols().add(out);
330 		} else if (de instanceof Context) {
331 		    Context ctx = (Context) de;
332 		    if (ctx.getLabel() == null) {
333 			ctx.setLabel(new TextLine("", HelpMethods.createList(out)));
334 		    } else {
335 			((TextLine) ctx.getLabel()).getSymbols().add(out);
336 		    }
337 		} else {
338 		    throw new Kite9ProcessingException("No glyph/text line/context to add the symbol to");
339 		}
340 	    }
341 
342 	};
343     }
344 
345     public ClassBuilder withClasses(Class<?>... forClasses) {
346 	List<Tie<? extends Object, Class<?>>> ties = new ArrayList<Tie<? extends Object, Class<?>>>(forClasses.length);
347 	for (Class<?> c : forClasses) {
348 	    ties.add(new Tie<Object, Class<?>>(null, null, c));
349 	}
350 	ClassBuilder ch = new ClassBuilder(ties, pm);
351 	return ch;
352     }
353 
354     public PackageBuilder withPackages(Package... packages) {
355 	List<Tie<? extends Object, Package>> ties = new ArrayList<Tie<? extends Object, Package>>(packages.length);
356 	for (Package p : packages) {
357 	    ties.add(new Tie<Object, Package>(null, null, p));
358 	}
359 	PackageBuilder ch = new PackageBuilder(ties, pm);
360 	return ch;
361     }
362 
363     public PackageBuilder withPackages(Class<?>... packagesForClasses) {
364 	List<Tie<? extends Object, Package>> ties = new ArrayList<Tie<? extends Object, Package>>(
365 		packagesForClasses.length);
366 	for (Class<?> c : packagesForClasses) {
367 	    ties.add(new Tie<Object, Package>(null, null, c.getPackage()));
368 	}
369 	PackageBuilder ch = new PackageBuilder(ties, pm);
370 	return ch;
371     }
372 
373     /***
374      * Filters methods, fields, inner classes to just the ones with a K9
375      * annotation.
376      */
377     public Filter<AnnotatedElement> onlyAnnotated() {
378 	return new Filter<AnnotatedElement>() {
379 	    public boolean accept(AnnotatedElement o) {
380 		K9OnDiagram on = null;
381 		if (o instanceof AnnotatedElement) {
382 		    AnnotatedElement ae = (AnnotatedElement) o;
383 		    on = ae.getAnnotation(K9OnDiagram.class);
384 		}
385 
386 		if (on != null) {
387 		    if ((on.on().length == 0))
388 			return true;
389 
390 		    for (Class<?> on1 : on.on()) {
391 			if (on1.equals(creator.getDeclaringClass())) {
392 			    return true;
393 			}
394 		    }
395 		}
396 
397 		return false;
398 	    }
399 	};
400     }
401 
402     /***
403      * Filters classes to just the ones mentioned in the arguments
404      */
405     public Filter<Class<?>> only(final Class<?>... classes) {
406 	return new Filter<Class<?>>() {
407 
408 	    public boolean accept(Class<?> o) {
409 		for (Class<?> class1 : classes) {
410 		    if (class1.equals(o))
411 			return true;
412 		}
413 
414 		return false;
415 	    }
416 	};
417     }
418 }