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 }