| GedcomInputHandlerImpl.java |
1 /****************************************************************************
2 * This demo file is part of yFiles for Java 2.14.
3 * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4 * 72070 Tuebingen, Germany. All rights reserved.
5 *
6 * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7 * of demo files in source code or binary form, with or without
8 * modification, is not permitted.
9 *
10 * Owners of a valid software license for a yFiles for Java version that this
11 * demo is shipped with are allowed to use the demo source code as basis
12 * for their own yFiles for Java powered applications. Use of such programs is
13 * governed by the rights and conditions as set out in the yFiles for Java
14 * license agreement.
15 *
16 * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19 * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 ***************************************************************************/
28 package demo.layout.genealogy.iohandler;
29
30 import y.base.DataAcceptor;
31 import y.base.DataProvider;
32 import y.base.Node;
33 import y.layout.genealogy.FamilyTreeLayouter;
34 import y.view.EdgeRealizer;
35 import y.view.Graph2D;
36 import y.view.NodeLabel;
37 import y.view.NodeRealizer;
38 import y.view.SmartNodeLabelModel;
39 import y.view.YLabel;
40
41 import java.awt.Color;
42 import java.util.HashMap;
43 import java.util.Map;
44 import java.util.Set;
45
46 /**
47 * Gets the information of a GEDCOM line and builds a graph with it.
48 * <p/>
49 * This class provides several callback methods to customize the handling of GEDCOM tags. In this implementation, the
50 * tags INDI, FAM, WIFE, HUSB and CHIL are used to get the graph structure and the tags NAME, BIRT, DEAT and DATE are
51 * used to create labels with the according information.
52 */
53 public class GedcomInputHandlerImpl implements GedcomInputHandler {
54 public static final Color DEFAULT_COLOR_FEMALE = new Color(204, 204, 255);
55 public static final Color DEFAULT_COLOR_MALE = new Color(255, 204, 153);
56 private static final String LABEL_CONFIG = "CroppingLabel";
57
58 private final Graph2D graph;
59 private final Map ids2nodes;
60 private Node currentNode;
61 private NodeLabel currentNodeLabel;
62
63 /**
64 * Initializes a new <code>GedcomInputHandlerImpl</code> for the given
65 * graph structure.
66 */
67 public GedcomInputHandlerImpl(Graph2D graph) {
68 this.graph = graph;
69 ids2nodes = new HashMap();
70 }
71
72 /**
73 * Returns the graph structure for which this handler was created.
74 * @return the graph structure for which this handler was created.
75 */
76 protected Graph2D getGraph() {
77 return graph;
78 }
79
80 /**
81 * Reacts to the beginning of the GEDCOM file. By default nothing happens.
82 */
83 public void handleStartDocument() {
84 }
85
86 /**
87 * Reacts to the end of the GEDCOM file. By default nothing happens.
88 */
89 public void handleEndDocument() {
90 }
91
92 /**
93 * Processes the given information according to the value of the tag.
94 *
95 * @param id the id from the GEDCOM line (might be <code>null</code>)
96 * @param tag the tag from the GEDCOM line
97 * @param value the value from the GEDCOM line (might be <code>null</code>)
98 */
99 public void handleStartTag(int level, String id, String tag, String value) {
100 if ("INDI".equals(tag)) {
101 handleIndividualTag(graph, level, id);
102 } else if ("FAM".equals(tag)) {
103 handleFamilyTag(graph, level, id);
104 } else if ("NAME".equals(tag)) {
105 handleNameTag(graph, currentNode, level, value);
106 } else if ("SEX".equals(tag)) {
107 handleSexTag(graph, currentNode, level, value);
108 } else if ("BIRT".equals(tag)) {
109 handleBirthTag(graph, currentNode, level, value);
110 } else if ("DEAT".equals(tag)) {
111 handleDeathTag(graph, currentNode, level, value);
112 } else if ("DATE".equals(tag)) {
113 handleDateTag(graph, currentNode, currentNodeLabel, level, value);
114 } else if ("HUSB".equals(tag)) {
115 handleHusbandTag(graph, currentNode, level, value);
116 } else if ("WIFE".equals(tag)) {
117 handleWifeTag(graph, currentNode, level, value);
118 } else if ("CHIL".equals(tag)) {
119 handleChildTag(graph, currentNode, level, value);
120 } else {
121 handleMiscTag(graph, currentNode, currentNodeLabel, level, id, tag, value);
122 }
123 }
124
125 /**
126 * Cleans up when a level ends.
127 *
128 * @param tag the tag of the level that ends
129 */
130 public void handleEndTag(int level, String tag) {
131 handleEndTag(graph, currentNode, currentNodeLabel, level, tag);
132 if ("INDI".equals(tag) || "FAM".equals(tag)) {
133 currentNode = null;
134 } else if ("NAME".equals(tag) || "BIRT".equals(tag) || "DEAT".equals(tag) || "MARR".equals(tag)
135 || "DATE".equals(tag)) {
136 currentNodeLabel = null;
137 }
138 }
139
140 protected void handleEndTag(Graph2D graph, Node node, NodeLabel label, int level, String tag) {
141 }
142
143 /**
144 * Starts building an individual node in the graph.
145 *
146 * @param graph the graph that is built
147 * @param id the id of the individual
148 */
149 protected void handleIndividualTag(Graph2D graph, int level, String id) {
150 currentNode = (Node) ids2nodes.get(id.trim());
151 if (currentNode == null) {
152 currentNode = createIndividualNode(graph);
153 ids2nodes.put(id.trim(), currentNode);
154 }
155 }
156
157 /**
158 * Creates a node for an individual in the family tree.
159 * <p/>
160 * To customize the representation of individuals,
161 * this method may be overwritten.
162 *
163 * @param graph the graph to be built
164 * @return the new individual node
165 */
166 protected Node createIndividualNode(Graph2D graph) {
167 return graph.createNode(createIndividualNodeRealizer(graph));
168 }
169
170 /**
171 * Creates the visual representation of an individual.
172 * <p/>
173 * To customize the visual representation of individuals,
174 * this method may be overwritten.
175 *
176 * @param graph the graph to be built
177 * @return the node realizer representing an individual
178 */
179 protected NodeRealizer createIndividualNodeRealizer(Graph2D graph) {
180 return graph.getDefaultNodeRealizer().createCopy();
181 }
182
183 /**
184 * Starts building a family node in the graph.
185 *
186 * @param graph the graph to be built
187 * @param id the id of the family
188 */
189 protected void handleFamilyTag(Graph2D graph, int level, String id) {
190 currentNode = (Node) ids2nodes.get(id.trim());
191 if (currentNode == null) {
192 currentNode = createFamilyNode(graph);
193 DataProvider dp = graph.getDataProvider(FamilyTreeLayouter.DP_KEY_FAMILY_TYPE);
194 if (dp instanceof DataAcceptor) {
195 ((DataAcceptor) dp).set(currentNode, FamilyTreeLayouter.TYPE_FAMILY);
196 }
197 ids2nodes.put(id.trim(), currentNode);
198 }
199 }
200
201 /**
202 * Creates a node for a family in the family tree.
203 * <p/>
204 * To customize the representation of families,
205 * this method may be overwritten.
206 *
207 * @param graph the graph to be built
208 * @return the new family node
209 */
210 protected Node createFamilyNode(Graph2D graph) {
211 return graph.createNode(createFamilyNodeRealizer(graph));
212 }
213
214 /**
215 * Creates the visual representation of a family.
216 * <p/>
217 * To customize the visual representation of families,
218 * this method may be overwritten.
219 *
220 * @param graph the graph to be built
221 * @return the node realizer representing a family
222 */
223 protected NodeRealizer createFamilyNodeRealizer(Graph2D graph) {
224 return graph.getDefaultNodeRealizer().createCopy();
225 }
226
227 /**
228 * Starts adding a name to an individual.
229 * <p/>
230 * To customize the representation of the name this method may be overwritten.
231 *
232 * @param graph the graph to be built
233 * @param node the currently changed node the label belongs to
234 * @param value the name text
235 */
236 protected void handleNameTag(Graph2D graph, Node node, int level, String value) {
237 if (node != null) {
238 final String name = value.replaceFirst("/", "\n").replaceAll("/", " ").trim();
239 final NodeLabel label = createLabel(name);
240 final SmartNodeLabelModel model = new SmartNodeLabelModel();
241 label.setLabelModel(model,
242 model.createDiscreteModelParameter(SmartNodeLabelModel.POSITION_TOP));
243 graph.getRealizer(node).setLabel(label);
244 }
245 }
246
247 /**
248 * Creates the label for the name of an individual.
249 * <p/>
250 * To customize the representation of the name this method may be overwritten.
251 *
252 * @param name the name text
253 * @return the label with the name
254 */
255 protected NodeLabel createLabel(String name) {
256 currentNodeLabel = new NodeLabel(name);
257 return currentNodeLabel;
258 }
259
260 /**
261 * Changes the color of the current node and adds an entry in the <code>DataProvider</code> for the {@link
262 * FamilyTreeLayouter} according to the sex of the individual.
263 * <p/>
264 * To customize the reaction to this tag, this method may be overwritten.
265 *
266 * @param graph the graph to be built
267 * @param node the currently changed node
268 * @param value the indication of the sex (F=female, M=male)
269 */
270 protected void handleSexTag(Graph2D graph, Node node, int level, String value) {
271 if (node != null) {
272 String type;
273 Color color;
274 if ("F".equals(value)) {
275 type = FamilyTreeLayouter.TYPE_FEMALE;
276 color = DEFAULT_COLOR_FEMALE;
277 } else {
278 type = FamilyTreeLayouter.TYPE_MALE;
279 color = DEFAULT_COLOR_MALE;
280 }
281 final NodeRealizer realizer = graph.getRealizer(node);
282 realizer.setFillColor(color);
283 realizer.setLineColor(null);
284 DataProvider dp = graph.getDataProvider(FamilyTreeLayouter.DP_KEY_FAMILY_TYPE);
285 if (dp instanceof DataAcceptor) {
286 ((DataAcceptor) dp).set(node, type);
287 }
288 }
289 }
290
291 /**
292 * Starts creating a label for the date of birth of an individual.
293 * <p/>
294 * To customize the representation to the date of birth, this method may be overwritten.
295 *
296 * @param graph the graph to be built
297 * @param node the currently changed node
298 * @param value the value of the GEDCOM line
299 */
300 protected void handleBirthTag(Graph2D graph, Node node, int level, String value) {
301 if (node != null) {
302 final NodeLabel label = createLabel("* ");
303 Set configurations = NodeLabel.getFactory().getAvailableConfigurations();
304 if (configurations.contains(LABEL_CONFIG)) {
305 label.setConfiguration(LABEL_CONFIG);
306 label.setAutoSizePolicy(YLabel.AUTOSIZE_NONE);
307 label.setContentSize(graph.getRealizer(node).getWidth() * 0.5, 20.0);
308 }
309 label.setAlignment(YLabel.ALIGN_LEFT);
310 final SmartNodeLabelModel model = new SmartNodeLabelModel();
311 label.setLabelModel(model,
312 model.createDiscreteModelParameter(SmartNodeLabelModel.POSITION_BOTTOM_LEFT));
313 graph.getRealizer(node).addLabel(label);
314 }
315 }
316
317 /**
318 * Starts creating a label for the date of death of an individual.
319 * <p/>
320 * To customize the representation of the date of death, this method may be overwritten.
321 *
322 * @param graph the graph to be built
323 * @param node the currently changed node
324 * @param value the value of the GEDCOM line
325 */
326 protected void handleDeathTag(Graph2D graph, Node node, int level, String value) {
327 if (node != null) {
328 final NodeLabel label = createLabel("\u271D ");
329 Set configurations = NodeLabel.getFactory().getAvailableConfigurations();
330 if (configurations.contains(LABEL_CONFIG)) {
331 label.setConfiguration(LABEL_CONFIG);
332 label.setAutoSizePolicy(YLabel.AUTOSIZE_NONE);
333 label.setContentSize(graph.getRealizer(node).getWidth() * 0.5, 20.0);
334 }
335 label.setAlignment(YLabel.ALIGN_RIGHT);
336 final SmartNodeLabelModel model = new SmartNodeLabelModel();
337 label.setLabelModel(model,
338 model.createDiscreteModelParameter(SmartNodeLabelModel.POSITION_BOTTOM_RIGHT));
339 graph.getRealizer(node).addLabel(label);
340 }
341 }
342
343 /**
344 * Adds the date text to a currently active label.
345 * <p/>
346 * This implementation writes the date as it comes from the GEDCOM file. To customize the representation of dates,
347 * this method may be overwritten.
348 *
349 * @param graph the graph to be built
350 * @param node the currently changed node
351 * @param label the currently changed label
352 * @param value the date text
353 */
354 protected void handleDateTag(Graph2D graph, Node node, NodeLabel label, int level, String value) {
355 if (label != null) {
356 label.setText(label.getText() + value);
357 }
358 }
359
360 /**
361 * Creates an edge between the wife node identified by the given id and the currently active family node.
362 * <p/>
363 * To customize the creation of this edge, this method may be overwritten.
364 *
365 * @param graph the graph to be built
366 * @param node the currently changed family node
367 * @param id the id of the wife
368 */
369 protected void handleWifeTag(Graph2D graph, Node node, int level, String id) {
370 if (node != null && isValidID(id)) {
371 Node wife = getNodeByID(id);
372 if (wife == null && id.length() > 0) {
373 wife = createIndividualNode(graph);
374 ids2nodes.put(id, wife);
375 }
376 graph.createEdge(wife, node, createWifeFamilyEdgeRealizer(graph));
377 }
378 }
379
380 /**
381 * Creates a realizer for an edge between the wife node and the family node.
382 * <p/>
383 * To customize the representation of the edge from the wife to the family node, this method may be overwritten.
384 *
385 * @param graph the graph to be built
386 * @return the edge realizer
387 */
388 protected EdgeRealizer createWifeFamilyEdgeRealizer(Graph2D graph) {
389 return graph.getDefaultEdgeRealizer().createCopy();
390 }
391
392 /**
393 * Creates an edge between the husband node identified by the given id and the currently active family node.
394 * <p/>
395 * To customize the creation of this edge, this method may be overwritten.
396 *
397 * @param graph the graph to be built
398 * @param node the currently changed family node
399 * @param id the id of the husband
400 */
401 protected void handleHusbandTag(Graph2D graph, Node node, int level, String id) {
402 if (node != null && isValidID(id)) {
403 Node husband = getNodeByID(id);
404 if (husband == null) {
405 husband = createIndividualNode(graph);
406 ids2nodes.put(id, husband);
407 }
408 graph.createEdge(husband, node, createHusbandFamilyEdgeRealizer(graph));
409 }
410 }
411
412 /**
413 * Creates a realizer for an edge between the husband node and the family node.
414 * <p/>
415 * To customize the representation of the edge from the husband to the family node, this method may be overwritten.
416 *
417 * @param graph the graph to be built
418 * @return the edge realizer
419 */
420 protected EdgeRealizer createHusbandFamilyEdgeRealizer(Graph2D graph) {
421 return graph.getDefaultEdgeRealizer().createCopy();
422 }
423
424 /**
425 * Creates an edge between the child node identified by the given id and the currently active family node.
426 * <p/>
427 * To customize the creation of this edge, this method may be overwritten.
428 *
429 * @param graph the graph to be built
430 * @param node the currently changed family node
431 * @param id the id of the child
432 */
433 protected void handleChildTag(Graph2D graph, Node node, int level, String id) {
434 if (node != null && isValidID(id)) {
435 Node child = getNodeByID(id);
436 if (child == null && id.length() > 0) {
437 child = createIndividualNode(graph);
438 ids2nodes.put(id, child);
439 }
440 graph.createEdge(node, child, createFamilyChildEdgeRealizer(graph));
441 }
442 }
443
444 /**
445 * Creates a realizer for an edge between the family node and the child node.
446 * <p/>
447 * To customize the representation of the edge from the family to the child node, this method may be overwritten.
448 *
449 * @param graph the graph to be built
450 * @return the edge realizer
451 */
452 protected EdgeRealizer createFamilyChildEdgeRealizer(Graph2D graph) {
453 return graph.getDefaultEdgeRealizer().createCopy();
454 }
455
456 /**
457 * Handles miscellaneous GEDCOM tags.
458 * <p/>
459 * By default nothing happens. To handle a custom range of tags, overwrite this method.
460 *
461 * @param graph the graph to be built
462 * @param node the currently changed node
463 * @param label the currently changed label
464 * @param id the id from the GEDCOM line (might be <code>null</code>)
465 * @param tag the tag from the GEDCOM line
466 * @param value the value from the GEDCOM line (might be <code>null</code>)
467 */
468 protected void handleMiscTag(Graph2D graph, Node node, NodeLabel label,
469 int level, String id, String tag, String value) {
470 }
471
472 /**
473 * Returns the node of the specified id.
474 *
475 * @param id the id of the node to return
476 * @return the node of the specified id
477 */
478 public Node getNodeByID(String id) {
479 return (Node) ids2nodes.get(id);
480 }
481
482 /**
483 * Validates if the given id is a GEDCOM id. The id has to start and end with an @.
484 *
485 * @param id the id to be checked
486 * @return <code>true</code>, if the id is valid, <code>false</code> otherwise
487 */
488 static boolean isValidID(String id) {
489 return id != null && id.startsWith("@") && id.endsWith("@");
490 }
491 }