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
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
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
228 return;
229 }
230
231
232 if ((ctxEl instanceof Container) && (((Container) ctxEl).getContents().contains(valueEl))) {
233 return;
234 }
235
236
237 if ((ctxEl instanceof Arrow) && (valueEl instanceof Arrow)) {
238 throw new Kite9ProcessingException("Can't currently handle connecting two arrows " + ctxEl + " " + valueEl);
239 }
240
241
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
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
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
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 }